Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/dismissable-info-messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@platforma-open/milaboratories.sequence-properties.model': minor
'@platforma-open/milaboratories.sequence-properties.ui': minor
'@platforma-open/milaboratories.sequence-properties': minor
---

Closeable info messages on the Main tab. The advisory alerts emitted by
the workflow (VHH detection, partial-region inputs, peptide-instability
floor, etc.) now show a close button. Dismissals persist in
`BlockData.dismissedInfoMessages` — server-side, across project reopens
and clients. The Settings modal includes a "Reset dismissed info
messages" action to clear all dismissals at once.

Model schema: new `Ver_2026_05_27` migration step backfills
`dismissedInfoMessages: []` on existing projects.
43 changes: 37 additions & 6 deletions model/src/dataModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// deleted a `.migrate(...)` line while keeping the named callback exported.

import { describe, expect, it } from "vitest";
import type { BlockData, BlockDataV1, BlockDataV2 } from "./types";
import { migrateV1toV2, migrateV2toV2_1 } from "./dataModel";
import type { BlockData, BlockDataV1, BlockDataV2, BlockDataV2_1 } from "./types";
import { migrateV1toV2, migrateV2toV2_1, migrateV2_1toV2_2 } from "./dataModel";

const tableState = {
pTableParams: {
Expand All @@ -33,31 +33,62 @@ const v2Graph: Pick<BlockDataV2, "graphStateScatter" | "graphStateHistogram"> =
describe("blockDataModel Ver_2026_05_18 backfill", () => {
it("backfills both label fields to '' on a bare V2 payload", () => {
const v2: BlockDataV2 = { tableState, ...v2Graph };
const upgraded: BlockData = migrateV2toV2_1(v2);
const upgraded: BlockDataV2_1 = migrateV2toV2_1(v2);
expect(upgraded.customBlockLabel).toBe("");
expect(upgraded.defaultBlockLabel).toBe("");
});

it("preserves an interim-deployed customBlockLabel", () => {
const v2: BlockDataV2 = { tableState, ...v2Graph, customBlockLabel: "X" };
const upgraded: BlockData = migrateV2toV2_1(v2);
const upgraded: BlockDataV2_1 = migrateV2toV2_1(v2);
expect(upgraded.customBlockLabel).toBe("X");
expect(upgraded.defaultBlockLabel).toBe("");
});

it("preserves an interim-deployed defaultBlockLabel", () => {
const v2: BlockDataV2 = { tableState, ...v2Graph, defaultBlockLabel: "Y" };
const upgraded: BlockData = migrateV2toV2_1(v2);
const upgraded: BlockDataV2_1 = migrateV2toV2_1(v2);
expect(upgraded.defaultBlockLabel).toBe("Y");
expect(upgraded.customBlockLabel).toBe("");
});

it("runs the full V1 → V2 → V2.1 chain on legacy data", () => {
const v1: BlockDataV1 = { tableState, defaultBlockLabel: "Old" };
const upgraded: BlockData = migrateV2toV2_1(migrateV1toV2(v1));
const upgraded: BlockDataV2_1 = migrateV2toV2_1(migrateV1toV2(v1));
expect(upgraded.defaultBlockLabel).toBe("Old");
expect(upgraded.customBlockLabel).toBe("");
expect(upgraded.graphStateScatter).toBeDefined();
expect(upgraded.graphStateHistogram).toBeDefined();
});
});

describe("blockDataModel Ver_2026_05_27 backfill", () => {
const baseV2_1: BlockDataV2_1 = {
tableState,
...v2Graph,
defaultBlockLabel: "",
customBlockLabel: "",
};

it("backfills dismissedInfoMessages to [] on a bare V2.1 payload", () => {
const upgraded: BlockData = migrateV2_1toV2_2(baseV2_1);
expect(upgraded.dismissedInfoMessages).toEqual([]);
});

it("preserves an interim-deployed dismissedInfoMessages array", () => {
const v2_1WithInterim = {
...baseV2_1,
dismissedInfoMessages: ["already-dismissed"],
};
const upgraded: BlockData = migrateV2_1toV2_2(v2_1WithInterim);
expect(upgraded.dismissedInfoMessages).toEqual(["already-dismissed"]);
});

it("runs the full V1 → V2 → V2.1 → V2.2 chain on legacy data", () => {
const v1: BlockDataV1 = { tableState, defaultBlockLabel: "Old" };
const upgraded: BlockData = migrateV2_1toV2_2(migrateV2toV2_1(migrateV1toV2(v1)));
expect(upgraded.defaultBlockLabel).toBe("Old");
expect(upgraded.customBlockLabel).toBe("");
expect(upgraded.dismissedInfoMessages).toEqual([]);
});
});
19 changes: 16 additions & 3 deletions model/src/dataModel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { GraphMakerState } from "@milaboratories/graph-maker";
import { createPlDataTableStateV2, DataModelBuilder } from "@platforma-sdk/model";
import type { BlockData, BlockDataV1, BlockDataV2 } from "./types";
import type { BlockData, BlockDataV1, BlockDataV2, BlockDataV2_1 } from "./types";

const DEFAULT_SCATTER_STATE: GraphMakerState = {
title: "Property Relationships",
Expand All @@ -23,12 +23,23 @@ export const migrateV1toV2 = (v1: BlockDataV1): BlockDataV2 => ({
graphStateHistogram: { ...DEFAULT_HISTOGRAM_STATE },
});

export const migrateV2toV2_1 = (v2: BlockDataV2): BlockData => ({
export const migrateV2toV2_1 = (v2: BlockDataV2): BlockDataV2_1 => ({
...v2,
defaultBlockLabel: v2.defaultBlockLabel ?? "",
customBlockLabel: v2.customBlockLabel ?? "",
});

// Backfills the persisted-dismissal list. `?? []` preserves any value an
// interim deployment may have written; missing → empty array. The UI
// filters info-alert strings via Set membership, so empty array means
// "show all messages".
export const migrateV2_1toV2_2 = (
v2_1: BlockDataV2_1 & { dismissedInfoMessages?: string[] },
): BlockData => ({
...v2_1,
dismissedInfoMessages: v2_1.dismissedInfoMessages ?? [],
});

export const blockDataModel = new DataModelBuilder()
.from<BlockDataV1>("Ver_2026_04_28")
// Already-deployed step. Future field additions must go into a new step
Expand All @@ -39,11 +50,13 @@ export const blockDataModel = new DataModelBuilder()
// interim-deployed value; missing fields default to "". The args
// projection (resolveTraceLabel in label.ts) requires both fields to be
// strings, never undefined.
.migrate<BlockData>("Ver_2026_05_18", migrateV2toV2_1)
.migrate<BlockDataV2_1>("Ver_2026_05_18", migrateV2toV2_1)
.migrate<BlockData>("Ver_2026_05_27", migrateV2_1toV2_2)
.init(() => ({
tableState: createPlDataTableStateV2(),
defaultBlockLabel: "",
customBlockLabel: "",
graphStateScatter: { ...DEFAULT_SCATTER_STATE },
graphStateHistogram: { ...DEFAULT_HISTOGRAM_STATE },
dismissedInfoMessages: [],
}));
1 change: 1 addition & 0 deletions model/src/label.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const base: Omit<BlockData, "customBlockLabel" | "defaultBlockLabel"> = {
template: "bins",
title: "Property Distribution",
} as BlockData["graphStateHistogram"],
dismissedInfoMessages: [],
};

const make = (custom: string, def: string): BlockData => ({
Expand Down
13 changes: 12 additions & 1 deletion model/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,22 @@ export type BlockDataV2 = Omit<BlockDataV1, "defaultBlockLabel"> & {
graphStateHistogram: GraphMakerState;
};

export type BlockData = Omit<BlockDataV2, "defaultBlockLabel"> & {
// V2.1 shape — what the deployed Ver_2026_05_18 migration produces. Input
// to the new Ver_2026_05_27 step that adds dismissedInfoMessages. Both
// label fields are required here (Ver_2026_05_18 backfills them).
export type BlockDataV2_1 = Omit<BlockDataV2, "defaultBlockLabel"> & {
defaultBlockLabel: string;
customBlockLabel: string;
};

// Current shape — output of Ver_2026_05_27. Adds dismissedInfoMessages,
// the persistent list of info-alert strings the user has closed.
// Workflow `info.messages` strings are deterministic per input, so the
// string content itself is a stable dismissal key.
export type BlockData = BlockDataV2_1 & {
dismissedInfoMessages: string[];
};

export type BlockArgs = {
inputAnchor: PlRef;
traceLabel: string;
Expand Down
54 changes: 27 additions & 27 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ catalog:
"@milaboratories/ts-builder": 1.4.0
"@milaboratories/ts-configs": 1.2.3
"@platforma-sdk/workflow-tengo": 5.24.0
"@platforma-sdk/block-tools": 2.8.1
"@platforma-sdk/block-tools": 2.9.2
"@platforma-sdk/model": 1.77.0
"@platforma-sdk/ui-vue": 1.77.0
"@platforma-sdk/test": 1.77.1
"@milaboratories/helpers": 1.14.2
"@platforma-sdk/tengo-builder": 2.5.29
"@platforma-sdk/tengo-builder": 3.0.5
"@platforma-sdk/package-builder": 3.12.0
"@platforma-sdk/blocks-deps-updater": 2.2.0
"@platforma-sdk/eslint-config": 1.2.0
Expand Down
1 change: 1 addition & 0 deletions ui/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ScatterPage from "./pages/ScatterPage.vue";

export const sdkPlugin = defineAppV3(platforma, (app) => {
app.model.data.customBlockLabel ??= "";
app.model.data.dismissedInfoMessages ??= [];

watchEffect(() => {
const anchor = app.model.data.inputAnchor;
Expand Down
Loading
Loading