From e101a637fa12d0af9d80538ed9026256544f8f92 Mon Sep 17 00:00:00 2001 From: Paul Newling Date: Mon, 18 May 2026 13:57:41 -0700 Subject: [PATCH 01/27] feat: per-instance trace label via customBlockLabel || defaultBlockLabel Workflow resolves pl7.app/trace.label to the user's PlBlockPage subtitle override (customBlockLabel), falling back to the auto-derived dataset label (defaultBlockLabel) when no override is set. Replaces the hardcoded "Sequence Properties" string that made multiple co-instances indistinguishable in Lead Selection and other downstream pickers. Model now stores customBlockLabel in BlockData and projects both labels into BlockArgs; UI binds v-model:subtitle and :subtitle-placeholder on PlBlockPage so the user can type an override. Migration defaults both fields to "" for pre-existing V1 projects. Same pattern as clonotype-clustering and titeseq-analysis PR #13. --- .changeset/trace-label-per-instance.md | 8 ++++++++ model/src/dataModel.ts | 4 ++++ model/src/index.ts | 4 +++- model/src/types.ts | 12 ++++++++---- ui/src/app.ts | 2 ++ ui/src/pages/MainPage.vue | 5 ++++- workflow/src/main.tpl.tengo | 11 ++++++++++- 7 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 .changeset/trace-label-per-instance.md diff --git a/.changeset/trace-label-per-instance.md b/.changeset/trace-label-per-instance.md new file mode 100644 index 0000000..853932a --- /dev/null +++ b/.changeset/trace-label-per-instance.md @@ -0,0 +1,8 @@ +--- +'@platforma-open/milaboratories.sequence-properties.workflow': minor +'@platforma-open/milaboratories.sequence-properties.model': minor +'@platforma-open/milaboratories.sequence-properties.ui': minor +'@platforma-open/milaboratories.sequence-properties': minor +--- + +Per-instance trace label. The workflow resolves `pl7.app/trace.label` to `customBlockLabel || defaultBlockLabel` instead of the hardcoded "Sequence Properties". Two sequence-properties blocks on the same dataset now show distinguishable entries in Lead Selection and other downstream pickers when the user customizes the `PlBlockPage` subtitle. Mirrors titeseq-analysis PR #13. diff --git a/model/src/dataModel.ts b/model/src/dataModel.ts index 01f26b9..8686a07 100644 --- a/model/src/dataModel.ts +++ b/model/src/dataModel.ts @@ -21,11 +21,15 @@ export const blockDataModel = new DataModelBuilder() .from("Ver_2026_04_28") .migrate("Ver_2026_05_05", (v1) => ({ ...v1, + defaultBlockLabel: v1.defaultBlockLabel ?? "", + customBlockLabel: "", graphStateScatter: { ...DEFAULT_SCATTER_STATE }, graphStateHistogram: { ...DEFAULT_HISTOGRAM_STATE }, })) .init(() => ({ tableState: createPlDataTableStateV2(), + defaultBlockLabel: "", + customBlockLabel: "", graphStateScatter: { ...DEFAULT_SCATTER_STATE }, graphStateHistogram: { ...DEFAULT_HISTOGRAM_STATE }, })); diff --git a/model/src/index.ts b/model/src/index.ts index 8ee0f47..1adf042 100644 --- a/model/src/index.ts +++ b/model/src/index.ts @@ -41,6 +41,8 @@ export const platforma = BlockModelV3.create(blockDataModel) } return { inputAnchor: data.inputAnchor, + defaultBlockLabel: data.defaultBlockLabel, + customBlockLabel: data.customBlockLabel, }; }) .output("inputOptions", (ctx) => ctx.resultPool.getOptions(inputAnchorSpecs)) @@ -107,7 +109,7 @@ export const platforma = BlockModelV3.create(blockDataModel) return pCols.map((c) => ({ columnId: c.id, spec: c.spec }) satisfies PColumnIdAndSpec); }) .title(() => "Sequence Properties") - .subtitle((ctx) => ctx.data.defaultBlockLabel ?? "") + .subtitle((ctx) => ctx.data.customBlockLabel || ctx.data.defaultBlockLabel) .sections(() => [ { type: "link", href: "/", label: "Main" }, { type: "link", href: "/scatter", label: "Property Relationships" }, diff --git a/model/src/types.ts b/model/src/types.ts index 5d684d2..3544c77 100644 --- a/model/src/types.ts +++ b/model/src/types.ts @@ -6,19 +6,23 @@ import type { PlDataTableStateV2, PlRef } from "@platforma-sdk/model"; export type BlockDataV1 = { inputAnchor?: PlRef; tableState: PlDataTableStateV2; - // UI-only state. Tracks the selected input dataset's label so the block - // subtitle can reflect it — populated by the UI watcher in app.ts. Not - // projected into BlockArgs because the workflow does not consume it. + // Historically optional UI-only state. Required in the current BlockData + // shape (V2) and projected into BlockArgs so the workflow trace label can + // fall back to it when the user has not typed a custom subtitle. defaultBlockLabel?: string; }; -export type BlockData = BlockDataV1 & { +export type BlockData = Omit & { + defaultBlockLabel: string; + customBlockLabel: string; graphStateScatter: GraphMakerState; graphStateHistogram: GraphMakerState; }; export type BlockArgs = { inputAnchor: PlRef; + defaultBlockLabel: string; + customBlockLabel: string; }; export type WorkflowMode = diff --git a/ui/src/app.ts b/ui/src/app.ts index cef3edc..4242d95 100644 --- a/ui/src/app.ts +++ b/ui/src/app.ts @@ -6,6 +6,8 @@ import MainPage from "./pages/MainPage.vue"; import ScatterPage from "./pages/ScatterPage.vue"; export const sdkPlugin = defineAppV3(platforma, (app) => { + app.model.data.customBlockLabel ??= ""; + watchEffect(() => { const anchor = app.model.data.inputAnchor; const opts = app.model.outputs.inputOptions ?? []; diff --git a/ui/src/pages/MainPage.vue b/ui/src/pages/MainPage.vue index 3a9898c..075aef3 100644 --- a/ui/src/pages/MainPage.vue +++ b/ui/src/pages/MainPage.vue @@ -41,7 +41,10 @@ const tableSettings = usePlDataTableSettingsV2({