mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 04:25:46 +02:00
* feat(issues): add configurable table view Co-authored-by: multica-agent <github@multica.ai> * test(issues): cover table columns in page fixture Co-authored-by: multica-agent <github@multica.ai> * fix(issues): make table column picker interactive Co-authored-by: multica-agent <github@multica.ai> * fix(issues): repair quick create and virtualize table rows Co-authored-by: multica-agent <github@multica.ai> * fix(issues): keep pinned table cells opaque Co-authored-by: multica-agent <github@multica.ai> * fix(issues): anchor full-width table rows Co-authored-by: multica-agent <github@multica.ai> * fix(issues): consolidate table controls Co-authored-by: multica-agent <github@multica.ai> * fix(issues): harden table pagination and export Co-authored-by: multica-agent <github@multica.ai> * feat(issues): add table quick search Co-authored-by: multica-agent <github@multica.ai> * fix(issues): make table window filters, selection, and export authoritative Round-2 review fixes for the issues table (MUL-4797): - Send the agents-working filter as a server ids facet so matches on unfetched pages surface and total/pagination/export agree; a present- but-empty id list yields an empty window instead of an unfiltered one. - Reset surface selection when the membership window changes and act on selection ∩ visible rows in the batch toolbar, so batch actions, Export selected, and the count all share one authoritative set. - Materialize the full flat window while table grouping is active, and suspend hierarchy nesting / parent-based grouping until the window is complete so structure cannot reshuffle as pages arrive; suppress header facet-count badges while the table window is partial. - Resolve actor directories and the property catalog at export time and fail the export instead of writing Unknown* actors or dropping configured property columns on cold/errored lookups. - Append a unique id tie-break to the list/grouped ORDER BY and mirror it in compareIssuesForSort so offset pages are stable across same-timestamp ties. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): bound table structure window and align chip/transport/selection Round-3 review fixes for the issues table (MUL-4797): - Cap whole-window materialization at TABLE_STRUCTURE_MAX_WINDOW (1000): below it the remaining pages load automatically — hierarchy applies without scrolling to the last page — and above it grouping/hierarchy suspend with an explicit toolbar notice instead of triggering an unbounded workspace download from a persisted view option. - Give the agents-working chip the authoritative in-window running set (the ids-facet window query, shared key with the filter-on state) so its badge can no longer say 0 while the filter would find matches on unfetched pages; falls back to loaded-row scoping elsewhere. - Route ids-facet windows through a new POST /api/issues/query twin — hundreds of running-issue UUIDs overflow the ~8 KB GET request-line budget of common proxies. The body carries the same key/value pairs; the handler rebuilds the query string and delegates to ListIssues. - Reset surface selection during render (key-change pattern) instead of a post-commit effect, so no frame ever pairs new membership with the old selection. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): harden table auto-pagination against errors and stale totals Round-4 review fixes for the issues table (MUL-4797): - Stop the structure materialization loop (and the scroll sentinel) when the window query is in error state — a persistently failing page left hasNextPage true and isFetchingNextPage false after every attempt, so the ungated effect refired forever. Resuming is an explicit toolbar Retry. The advancement decision now lives in a pure, tested shouldAutoLoadNextStructurePage helper. - Make the structure ceiling a hard stop: the ceiling check reads the LATEST page's total (pagination already advances on it, so a stale small page-1 total could re-open unbounded materialization), and the loop additionally halts on loaded count >= ceiling regardless of any reported total. - Drive the working (ids-facet) window to completion — it is inherently bounded by the running set — and treat it as the chip's authoritative scope only when complete, so >100 running issues no longer under-count as a single page. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): make working-window pagination capped and unknown-aware Round-5 (final) review fixes for the issues table (MUL-4797): - The working (ids-facet) window now advances through the same shouldAutoLoadNextWindowPage gates as the structure loop — it shares the main table's cache key while the agents-working filter is on, so an uncapped chip-driven loop re-opened the very ceiling the table just enforced. An over-ceiling window stops after page one. - A cold-load failure of the flat window is an ERROR state, not an empty workspace: isEmpty only claims empty on a successful zero-result fetch, and the surface renders a dedicated failed-to-load state with a reachable Retry (the in-table Retry never mounted without data). - The chip scope is now tri-state honest: a COMPLETE window (or an empty running set) yields a precise count, keepPreviousData carries the last-known-complete set across re-keys, and everything else — cold resolving, failed, over the ceiling — presents as an explicit unknown ('Agents working: —') instead of a number derived from whichever incomplete window happened to be loaded. Co-authored-by: multica-agent <github@multica.ai> * fix(issues): single pagination owner and placeholder-honest chip scope Round-6 review fixes for the issues table (MUL-4797): - Exclude placeholder data from the working-window completeness gate: on a re-key (running set or facet change) keepPreviousData leaves the OLD key's rows visible, and pairing them with the new task snapshot published a precise-looking number for a scope nobody fetched. The scope now reads unknown until the new key resolves. - Make the shared table query single-owner while the agents-working filter is on: the chip's background loop no longer answers the same render snapshot as TableView's structure loop, and every auto caller (structure loop, working loop, scroll sentinel, retry) now uses fetchNextPage({cancelRefetch: false}) so a concurrent responder no-ops instead of cancel/restarting a fetch whose HTTP request is not abortable — which had been duplicating every offset. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
115 lines
3.3 KiB
TypeScript
115 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useMemo,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
import { useIssueSelectionStore } from "@multica/core/issues/stores/selection-store";
|
|
|
|
export interface IssueSurfaceSelection {
|
|
selectedIds: Set<string>;
|
|
toggle: (id: string) => void;
|
|
select: (ids: string[]) => void;
|
|
deselect: (ids: string[]) => void;
|
|
clear: () => void;
|
|
}
|
|
|
|
const IssueSurfaceSelectionContext =
|
|
createContext<IssueSurfaceSelection | null>(null);
|
|
|
|
export function useCreateIssueSurfaceSelection(
|
|
surfaceKey: string,
|
|
resetKey = surfaceKey,
|
|
): IssueSurfaceSelection {
|
|
const [selectedIds, setSelectedIds] = useState(() => new Set<string>());
|
|
const [committedResetKey, setCommittedResetKey] = useState(resetKey);
|
|
|
|
// Render-phase reset (the React "adjusting state when props change"
|
|
// pattern), NOT an effect: an effect runs after commit, so one frame would
|
|
// ship the NEW membership (filters/search) with the OLD selection — batch
|
|
// consumers could act on rows the window no longer contains during that
|
|
// gap. Setting state during render re-renders synchronously before commit,
|
|
// so no such frame ever becomes visible. The size guard keeps the common
|
|
// nothing-selected key change from allocating a new Set and re-rendering
|
|
// the surface.
|
|
if (committedResetKey !== resetKey) {
|
|
setCommittedResetKey(resetKey);
|
|
if (selectedIds.size > 0) setSelectedIds(new Set());
|
|
}
|
|
|
|
const toggle = useCallback((id: string) => {
|
|
setSelectedIds((current) => {
|
|
const next = new Set(current);
|
|
if (next.has(id)) next.delete(id);
|
|
else next.add(id);
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
const select = useCallback((ids: string[]) => {
|
|
setSelectedIds((current) => {
|
|
const next = new Set(current);
|
|
for (const id of ids) next.add(id);
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
const deselect = useCallback((ids: string[]) => {
|
|
setSelectedIds((current) => {
|
|
const next = new Set(current);
|
|
for (const id of ids) next.delete(id);
|
|
return next;
|
|
});
|
|
}, []);
|
|
|
|
const clear = useCallback(() => {
|
|
setSelectedIds(new Set());
|
|
}, []);
|
|
|
|
return useMemo(
|
|
() => ({ selectedIds, toggle, select, deselect, clear }),
|
|
[clear, deselect, select, selectedIds, toggle],
|
|
);
|
|
}
|
|
|
|
export function IssueSurfaceSelectionProvider({
|
|
selection,
|
|
children,
|
|
}: {
|
|
selection: IssueSurfaceSelection;
|
|
children: ReactNode;
|
|
}) {
|
|
return (
|
|
<IssueSurfaceSelectionContext.Provider value={selection}>
|
|
{children}
|
|
</IssueSurfaceSelectionContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useIssueSurfaceSelectionOptional() {
|
|
return useContext(IssueSurfaceSelectionContext);
|
|
}
|
|
|
|
export function useIssueSurfaceSelection(): IssueSurfaceSelection {
|
|
const surfaceSelection = useIssueSurfaceSelectionOptional();
|
|
const globalSelectedIds = useIssueSelectionStore((s) => s.selectedIds);
|
|
const globalToggle = useIssueSelectionStore((s) => s.toggle);
|
|
const globalSelect = useIssueSelectionStore((s) => s.select);
|
|
const globalDeselect = useIssueSelectionStore((s) => s.deselect);
|
|
const globalClear = useIssueSelectionStore((s) => s.clear);
|
|
|
|
return (
|
|
surfaceSelection ?? {
|
|
selectedIds: globalSelectedIds,
|
|
toggle: globalToggle,
|
|
select: globalSelect,
|
|
deselect: globalDeselect,
|
|
clear: globalClear,
|
|
}
|
|
);
|
|
}
|