mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-04 17:18:35 +02:00
* fix(ui): make table column resizing land where the pointer is (MUL-5166) Pressing the resize handle committed a width before any drag: it wrote the rendered width, which fixed table-layout had stretched past the configured one, so a stray click on a column edge silently rewrote and persisted that column and stopped it adapting to the window. Dragging then ran ahead of the pointer, because committing one width changes how much leftover space the layout has to share out and rescales every other column mid-gesture. And the gesture never ended if the pointer came up outside the window or the user switched apps — the column kept tracking the pointer on return, with the page stuck unselectable under a col-resize cursor. - Require 4px of travel before anything is committed, matching the column-reorder sensor's activation distance on the same header. - Pin every column to its rendered width on the frame the drag starts, so there is no leftover left to redistribute and the drag maps 1:1. - Capture the pointer and end on blur / pointercancel / lostpointercapture, the same four-part contract the sidebar rail already uses. - Own the cursor from a portaled full-viewport layer for the duration of the drag. document.body.style.cursor loses to any descendant that declares its own, which is why the cursor flickered over rows and text. Resizing also re-rendered every cell each frame, and each cell carries a popover, so a frame cost ~143ms. Widths now travel as custom properties published once on the <table>, and the body is swapped for a memoized copy while a drag is live — the browser applies each new width with no React work. Visually the table had no column rules at all, which left the pinned columns' trailing shadow as the only vertical line and made it read as a stray border on one column. Every column now carries a rule, the pinned shadow appears only once the surface is actually scrolled sideways, and the resize handle brightens its rule to brand rather than matching the faint resting colour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(issues): drag a table column by its header, not by a hidden grip Reordering columns was advertised by a grip that only appeared once the pointer was already on the header, and dragging it moved a wrapper the height of its own line of text while the <th> around it clipped anything that travelled outside the cell — so the column being moved looked like it had vanished rather than slid. The header is now the handle. There is no grip: the cursor carries the affordance (grab, then grabbing), the whole cell responds, and only the sort/hide button opts out, since a press there is always the menu. The wrapper spans the cell's box at all times — the negative margins undo the <th>'s padding and put it back inside — so what travels is a header-sized block and going translucent is the entire drag state, matching how a desktop tab behaves. Overflow is lifted for the length of a reorder and the column in hand is raised over its neighbours. Columns are restricted to the horizontal axis, the same constraint the desktop tab bar puts on tab reordering, which is why packages/views now declares @dnd-kit/modifiers directly. Transforming the <th> itself would carry the header's height along for free, but `transform` on a table cell is a corner of the spec browsers take liberties with — Chromium lifts the cell out of the table's box model and its geometry stops matching the row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(issues): stop a dragged table column stretching into its neighbour's width Dragging a column header applied dnd-kit's full transform, which carries scaleX/scaleY alongside the translation. Those come from its layout animation: old rect over new rect, tweening an item into the shape of the slot it lands in. Between two tabs of equal width the ratio is 1 and never shows. Between two columns it is not — swapping a 174px column with a 96px one stretched the header to 1.8x on the way across. Only the horizontal translation is applied now; reordering changes no column's width, so there is no shape for a tween to describe. The travel and the settle stay animated through `transition`. The travelling wrapper was also fixed at h-8 while the header strip measures 39px once row borders are counted, leaving the block short at both edges and misaligned by 3px — which read as the same flattening. Its height is derived from the cell now. The reorder grip returns as the drag handle, appearing on hover as before and staying visible for the length of a drag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ui): mark the frozen column boundary while the table is scrolled sideways The edge where the frozen columns end had a shadow drawn inside the pinned cell with `--border`. That token is tuned for hairlines between adjacent surfaces — oklch .945 in light mode — and all but disappears once spread into a shadow, so the boundary read as unmarked while content slid underneath it. Darkening it in place only made the frozen column look outlined: an inset shadow can shade that cell's own edge and nothing else, while the depth being described belongs to the content passing beneath. Split into the two things MUI X's data grid separates, a permanent border for where the boundary is and a shadow for something crossing it right now: - the rule between the frozen columns and the rest stays put, as before; - a 12px gradient is cast past the frozen block, mounted outside the scroll container and shown only while scrolled sideways. Its position is measured off the DOM — the trailing frozen header carries a `data-pinned-edge` marker — rather than summed from column sizes. Fixed table-layout stretches columns past their configured widths whenever the container is wider than the table's min-width, so a sum lands the marker in the middle of visible content. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(ui): size a table column to its content on double-click Double-clicking a column's resize handle called column.resetSize, which cleared the stored width and let TanStack fall back to its generic default of 150 — a number unrelated to any width this table was designed with. "Reset" therefore widened priority from 130, collapsed labels from 220, and clamped title to its 260 minimum. It restored nothing. It now sizes the column to its widest rendered cell, the convention Excel, Sheets, AG Grid and Notion all share for that gesture. Fixed table-layout ignores content and the cells truncate their own text, so nothing on screen reports the width the content wants. The measurement lifts both constraints across the column's cells, reads them, and restores everything within the same task, so the browser paints once — after the restore — and the intermediate layout is never seen. Only the rows inside the virtual window are measured. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ui): stop the table header flickering black during scroll The sticky header carried backdrop-blur over a translucent fill. A backdrop-filter on a sticky element with content scrolling beneath it is a known Chromium compositing fault — the blur layer recomputes its backdrop every frame, and virtualisation is adding and removing the very rows it reads from, so the strip flickers black under fast scrolling. Chromium's fix for the same symptom was reverted, so upgrading does not carry it (electron#12906, electron#45854, chromium#339841685). The fill is now the opaque colour that bg-muted/30 was compositing to, which is the mix the pinned header cells already use. A header the content scrolls behind has no reason to show it through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(issues): give the table's title column back the width its hover actions held The sub-issue and rename buttons sat inline in the title cell at opacity 0. Hidden is not absent: their boxes reserved around 40px of the column at all times, in the one column with the least room to spare, for controls that only appear on hover. They are positioned over the cell's trailing edge now, the way SidebarMenuAction is, with a gradient fading the title running underneath rather than icons sitting on top of it — the sidebar needs no gradient because its labels are short, a title runs to the cell's edge. focus-within keeps them reachable from the keyboard, where hover never fires. Reordering a column also scrolled the table vertically. Modifiers constrain a drag's movement but not its auto-scrolling, which reads raw pointer coordinates, so a few pixels of vertical drift sent the rows moving under a gesture that cannot act on them. Auto-scroll's y threshold is zero now; x keeps it, since a table wider than its viewport needs it to reach a distant slot, but at 0.05 rather than the default 0.2, which arms it a fifth of the way in from either edge — most of a wide header. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(issues): show the table's own grid while its first page loads, and end its columns like every other surface Two loading-state gaps, both in how the table's non-issue rows are modelled. A cold load rendered a single "Loading…" line under a full header, which reads as an empty table rather than a loading one. The surface-level skeleton meant for it was unreachable: use-issue-surface-data hardcodes isLoading to false for table, so the `mode === "table"` branch never ran — and it would not have fitted, drawing rounded bars at p-2 where the table draws an edge-to-edge grid, so switching it on would have traded one wrong state for a layout jump. Placeholders are rows now, rendered through the ordinary cell renderer so they inherit the real column widths, pinning and borders. Everything the table knows before its data — header, toolbar, column layout — is up immediately, and the rows swap in without moving anything. The unreachable surface branch is gone. The end of a column was hand-rolled too, leaving the table the one surface where a failed page read as muted body text rather than an error, where the prompt sat left-aligned against three centred ones, and where reaching the end of a paginated branch said nothing at all. The row carries its state rather than a finished label and renders through ListLoadMoreFooter, the footer Board, List and Swimlane already share — which exists, per its own comment, so those states and their wording stay consistent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ui): measure table rows instead of assuming they are all one height Virtualisation sized every row at the same 41px estimate, but the table does not have one kind of row: group headers are 37px, an end-of-column footer shorter still, and placeholders different again. Each one put the estimate a few pixels out, and the error accumulates — with grouping on, the rendered window drifts off the rows it should be showing and the scrollbar overshoots the bottom. Rows report their own height through the virtualizer's measureElement now, and the estimate only covers rows that have never been mounted. Rows built by renderRow are cloned to carry data-index and the measuring ref, so callers keep returning a plain <tr> and still take part. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(issues): scope the title cell's actions to the title cell The sub-issue and rename buttons keyed off the row's hover state, so pointing anywhere along a row — a status chip, a date, empty space — put controls that act on the title under a pointer that was somewhere else. They follow the title cell's own hover now. The gradient behind them still tracks the row colour, since that is what the cell is painted with while they are showing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(ui): let group rows report their height, and re-measure the frozen edge on resize Self-review of the branch turned up two places where a change did not reach as far as it was supposed to. Group rows never took part in the row measurement. DataTable clones the virtualizer's ref and data-index onto whatever renderRow returns, but IssueTableGroupRow declared only its own three props and absorbed them — and a group header, being shorter than a data row, is exactly the row the measurement was added for. The frozen-column shadow measured its boundary from the scroll handler alone. Resizing a frozen column while the surface is scrolled sideways moves that boundary with no scroll event to follow, leaving the shadow behind at the old position. Also drops a dependency left behind when the load-more rows stopped building their own labels. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
136 lines
4.9 KiB
JSON
136 lines
4.9 KiB
JSON
{
|
|
"name": "@multica/views",
|
|
"version": "0.0.0",
|
|
"private": true,
|
|
"type": "module",
|
|
"scripts": {
|
|
"typecheck": "tsc --noEmit",
|
|
"lint": "eslint .",
|
|
"test": "vitest run"
|
|
},
|
|
"exports": {
|
|
"./navigation": "./navigation/index.ts",
|
|
"./common/actor-avatar": "./common/actor-avatar.tsx",
|
|
"./editor": "./editor/index.ts",
|
|
"./issues/components": "./issues/components/index.ts",
|
|
"./issues/hooks": "./issues/hooks/index.ts",
|
|
"./issues/utils/filter": "./issues/utils/filter.ts",
|
|
"./issues/utils/sort": "./issues/utils/sort.ts",
|
|
"./common/task-transcript": "./common/task-transcript/index.ts",
|
|
"./projects/components": "./projects/components/index.ts",
|
|
"./autopilots/components": "./autopilots/components/index.ts",
|
|
"./modals/registry": "./modals/registry.tsx",
|
|
"./modals/create-issue": "./modals/create-issue.tsx",
|
|
"./my-issues": "./my-issues/index.ts",
|
|
"./skills": "./skills/index.ts",
|
|
"./agents": "./agents/index.ts",
|
|
"./members": "./members/index.ts",
|
|
"./inbox": "./inbox/index.ts",
|
|
"./runtimes": "./runtimes/index.ts",
|
|
"./billing": "./billing/index.ts",
|
|
"./dashboard": "./dashboard/index.ts",
|
|
"./squads": "./squads/index.ts",
|
|
"./squads/components": "./squads/components/index.ts",
|
|
"./workspace/workspace-avatar": "./workspace/workspace-avatar.tsx",
|
|
"./workspace/create-workspace-form": "./workspace/create-workspace-form.tsx",
|
|
"./workspace/no-access-page": "./workspace/no-access-page.tsx",
|
|
"./workspace/new-workspace-page": "./workspace/new-workspace-page.tsx",
|
|
"./workspace/welcome-after-onboarding": "./workspace/welcome-after-onboarding.tsx",
|
|
"./workspace/use-workspace-seen": "./workspace/use-workspace-seen.ts",
|
|
"./layout": "./layout/index.ts",
|
|
"./layout/route-icon-components": "./layout/route-icon-components.tsx",
|
|
"./auth": "./auth/index.ts",
|
|
"./search": "./search/index.ts",
|
|
"./chat": "./chat/index.ts",
|
|
"./settings": "./settings/index.ts",
|
|
"./settings/lark-tab": "./settings/components/lark-tab.tsx",
|
|
"./lark": "./lark/index.ts",
|
|
"./slack": "./slack/index.ts",
|
|
"./invite": "./invite/index.ts",
|
|
"./invitations": "./invitations/index.ts",
|
|
"./onboarding": "./onboarding/index.ts",
|
|
"./platform": "./platform/index.ts",
|
|
"./i18n": "./i18n/index.ts",
|
|
"./attachments": "./attachments/index.ts",
|
|
"./locales": "./locales/index.ts",
|
|
"./locales/*": "./locales/*"
|
|
},
|
|
"dependencies": {
|
|
"@base-ui/react": "^1.3.0",
|
|
"@dnd-kit/core": "^6.3.1",
|
|
"@dnd-kit/modifiers": "^9.0.0",
|
|
"@dnd-kit/sortable": "^10.0.0",
|
|
"@dnd-kit/utilities": "^3.2.2",
|
|
"@floating-ui/dom": "^1.7.6",
|
|
"@multica/core": "workspace:*",
|
|
"@multica/ui": "workspace:*",
|
|
"@tanstack/react-virtual": "catalog:",
|
|
"@tiptap/core": "3.27.1",
|
|
"@tiptap/extension-code-block-lowlight": "3.27.1",
|
|
"@tiptap/extension-document": "3.27.1",
|
|
"@tiptap/extension-highlight": "3.27.1",
|
|
"@tiptap/extension-image": "3.27.1",
|
|
"@tiptap/extension-link": "3.27.1",
|
|
"@tiptap/extension-list": "3.27.1",
|
|
"@tiptap/extension-mention": "3.27.1",
|
|
"@tiptap/extension-paragraph": "3.27.1",
|
|
"@tiptap/extension-placeholder": "3.27.1",
|
|
"@tiptap/extension-table": "3.27.1",
|
|
"@tiptap/extension-table-cell": "3.27.1",
|
|
"@tiptap/extension-table-header": "3.27.1",
|
|
"@tiptap/extension-table-row": "3.27.1",
|
|
"@tiptap/extension-text": "3.27.1",
|
|
"@tiptap/extension-typography": "3.27.1",
|
|
"@tiptap/markdown": "3.27.1",
|
|
"@tiptap/pm": "3.27.1",
|
|
"@tiptap/react": "3.27.1",
|
|
"@tiptap/starter-kit": "3.27.1",
|
|
"@tiptap/suggestion": "3.27.1",
|
|
"cmdk": "^1.1.1",
|
|
"hast-util-to-html": "^9.0.5",
|
|
"katex": "catalog:",
|
|
"lowlight": "^3.3.0",
|
|
"mdast-util-from-markdown": "^2.0.3",
|
|
"mermaid": "catalog:",
|
|
"motion": "^12.38.0",
|
|
"pinyin-pro": "3.26.0",
|
|
"react-easy-crop": "^6.2.0",
|
|
"react-markdown": "^10.1.0",
|
|
"react-qr-code": "catalog:",
|
|
"react-resizable-panels": "^4.7.5",
|
|
"react-virtuoso": "catalog:",
|
|
"recharts": "3.8.0",
|
|
"rehype-katex": "catalog:",
|
|
"rehype-raw": "^7.0.0",
|
|
"rehype-sanitize": "^6.0.0",
|
|
"remark-breaks": "^4.0.0",
|
|
"remark-gfm": "^4.0.1",
|
|
"remark-math": "catalog:",
|
|
"sonner": "^2.0.7"
|
|
},
|
|
"peerDependencies": {
|
|
"@tanstack/react-query": "catalog:",
|
|
"@tanstack/react-table": "catalog:",
|
|
"i18next": "catalog:",
|
|
"lucide-react": "catalog:",
|
|
"react": "catalog:",
|
|
"react-dom": "catalog:",
|
|
"react-i18next": "catalog:",
|
|
"zustand": "catalog:"
|
|
},
|
|
"devDependencies": {
|
|
"@multica/eslint-config": "workspace:*",
|
|
"@multica/tsconfig": "workspace:*",
|
|
"@testing-library/jest-dom": "catalog:",
|
|
"@testing-library/react": "catalog:",
|
|
"@testing-library/user-event": "catalog:",
|
|
"@types/react": "catalog:",
|
|
"@types/react-dom": "catalog:",
|
|
"@vitejs/plugin-react": "catalog:",
|
|
"eslint-plugin-i18next": "catalog:",
|
|
"jsdom": "catalog:",
|
|
"typescript": "catalog:",
|
|
"vitest": "catalog:"
|
|
}
|
|
}
|