Compare commits

...

1 Commits

Author SHA1 Message Date
Jiang Bohan
8d6f089e16 fix(issues): show labels in my-issues view + place chips after title
- my-issues page lost labels because myIssuesViewStore cherry-picked
  name/storage/partialize from viewStorePersistOptions and dropped the
  cardProperties-aware merge. Persisted snapshots predating the labels
  toggle had cardProperties.labels = undefined, falsy-shorting the chip
  render. Extracted mergeViewStatePersisted as a generic and wired it
  into both stores.
- list-row chips now render right after the title (with a small left
  margin for breathing room) instead of in the right-aligned cluster.
2026-04-27 16:49:14 +08:00
4 changed files with 41 additions and 23 deletions

View File

@@ -6,6 +6,7 @@ import {
type IssueViewState,
viewStoreSlice,
viewStorePersistOptions,
mergeViewStatePersisted,
} from "./view-store";
import { registerForWorkspaceRehydration } from "../../platform/workspace-storage";
@@ -32,6 +33,11 @@ const _myIssuesViewStore = createStore<MyIssuesViewState>()(
...basePersist.partialize(state),
scope: state.scope,
}),
// Reuse the same deep-merge as the base view store so newly added
// cardProperties toggles inherit defaults for existing users. Without
// this, the my-issues page renders no labels because the persisted
// snapshot predates the `labels` key and shallow-merge wins.
merge: mergeViewStatePersisted<MyIssuesViewState>,
},
),
);

View File

@@ -212,19 +212,29 @@ export const viewStorePersistOptions = (name: string) => ({
// missing — the dropdown switch then reads `undefined` and renders unchecked
// even though defaults treat it as on. Deep-merge `cardProperties` so newly
// added toggles inherit their default value for existing users.
merge: (persisted: unknown, current: IssueViewState): IssueViewState => {
const p = (persisted ?? {}) as Partial<IssueViewState>;
return {
...current,
...p,
cardProperties: {
...current.cardProperties,
...(p.cardProperties ?? {}),
},
};
},
merge: mergeViewStatePersisted,
});
/**
* Reusable persist `merge` for view-state stores. Generic over T so the same
* deep-merge for `cardProperties` works for both the issues view store and
* the my-issues view store (which extends IssueViewState).
*/
export function mergeViewStatePersisted<T extends IssueViewState>(
persisted: unknown,
current: T,
): T {
const p = (persisted ?? {}) as Partial<T>;
return {
...current,
...p,
cardProperties: {
...current.cardProperties,
...(p.cardProperties ?? {}),
},
};
}
/** Factory: creates a vanilla StoreApi for use with React Context. */
export function createIssueViewStore(persistKey: string): StoreApi<IssueViewState> {
const store = createStore<IssueViewState>()(

View File

@@ -130,6 +130,7 @@ const mockViewState = {
vi.mock("@multica/core/issues/stores/view-store", () => ({
useClearFiltersOnWorkspaceChange: () => {},
viewStorePersistOptions: () => ({ name: "test", storage: undefined, partialize: (s: any) => s }),
mergeViewStatePersisted: (_p: unknown, c: any) => c,
viewStoreSlice: vi.fn(),
useIssueViewStore: Object.assign(
(selector?: any) => (selector ? selector(mockViewState) : mockViewState),
@@ -153,6 +154,7 @@ vi.mock("@multica/core/issues/stores/view-store", () => ({
{ key: "assignee", label: "Assignee" },
{ key: "dueDate", label: "Due date" },
{ key: "project", label: "Project" },
{ key: "labels", label: "Labels" },
{ key: "childProgress", label: "Sub-issue progress" },
],
}));

View File

@@ -91,19 +91,19 @@ export const ListRow = memo(function ListRow({
</span>
</span>
)}
{showLabels && (
<span className="ml-1.5 hidden md:inline-flex shrink-0 items-center gap-1 max-w-[260px] overflow-hidden">
{labels.slice(0, 3).map((label) => (
<LabelChip key={label.id} label={label} />
))}
{labels.length > 3 && (
<span className="text-[11px] text-muted-foreground">
+{labels.length - 3}
</span>
)}
</span>
)}
</span>
{showLabels && (
<span className="hidden md:inline-flex shrink-0 items-center gap-1 max-w-[260px] overflow-hidden">
{labels.slice(0, 3).map((label) => (
<LabelChip key={label.id} label={label} />
))}
{labels.length > 3 && (
<span className="text-[11px] text-muted-foreground">
+{labels.length - 3}
</span>
)}
</span>
)}
{showProject && (
<span className="inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground max-w-[140px]">
<ProjectIcon project={project} size="sm" />