Skip to content
Draft
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
14 changes: 14 additions & 0 deletions .changeset/dismissable-info-messages-minimal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@platforma-open/milaboratories.sequence-properties.ui': minor
'@platforma-open/milaboratories.sequence-properties': minor
---

Closeable info messages on the Main tab — session-only variant. The
advisory alerts emitted by the workflow now show a close button.
Dismissals live in a local UI ref and reset when the block UI unmounts
(project close, app reload). No `BlockData` change; no migration; the
block model is unchanged.

This is the minimal-surface alternative to a persisted-dismissal
approach. Pick this when you want "acknowledged for this session, fresh
on reopen" semantics and don't want to extend the block schema.
62 changes: 31 additions & 31 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
33 changes: 32 additions & 1 deletion ui/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { platforma } from "@platforma-open/milaboratories.sequence-properties.model";
import { defineAppV3 } from "@platforma-sdk/ui-vue";
import { watchEffect } from "vue";
import { ref, watch, watchEffect } from "vue";
import HistogramPage from "./pages/HistogramPage.vue";
import MainPage from "./pages/MainPage.vue";
import ScatterPage from "./pages/ScatterPage.vue";

// Module-level singleton — session-only dismissal of info-message alerts.
// Tied to currently-firing advisories: an entry stays here only while its
// string is in `outputs.info.messages`. The `watch` inside `sdkPlugin`
// prunes entries that the workflow no longer emits, so a message that
// "naturally" disappears doesn't carry over its dismissal to a future
// re-fire. Survives in-block route changes and switching away to other
// blocks (desktop LRU-caches block UIs, limit 4). Resets on project close,
// block reload, LRU eviction, or app restart. Hairpin-safe: writes happen
// only via user gesture (PlAlert close emit) or output → local-ref prune,
// both sanctioned by `harnesses/block-dev/hairpin.md`.
export const dismissedInfoMessages = ref(new Set<string>());

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

Expand All @@ -17,6 +29,25 @@ export const sdkPlugin = defineAppV3(platforma, (app) => {
app.model.data.defaultBlockLabel = match?.label ?? "";
});

// Auto-prune the dismissed set to the currently-firing messages. When
// the workflow stops emitting a previously-dismissed string (re-run
// with a different input, advisory naturally goes away), drop it from
// the set so a future re-fire shows fresh. Output → local Vue ref is a
// sanctioned pattern per `hairpin.md` (state lives in the JS context,
// not BlockData; no multi-client interleave).
watch(
() => app.model.outputs.info?.messages,
(messages) => {
const fired = new Set(messages ?? []);
const current = dismissedInfoMessages.value;
const filtered = [...current].filter((m) => fired.has(m));
if (filtered.length !== current.size) {
dismissedInfoMessages.value = new Set(filtered);
}
},
{ immediate: true },
);

return {
routes: {
"/": () => MainPage,
Expand Down
22 changes: 18 additions & 4 deletions ui/src/pages/MainPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import {
PlSlideModal,
usePlDataTableSettingsV2,
} from "@platforma-sdk/ui-vue";
import { ref, watch } from "vue";
import { useApp } from "../app";
import { computed, ref, watch } from "vue";
import { dismissedInfoMessages, useApp } from "../app";

const app = useApp();

Expand All @@ -35,6 +35,17 @@ function setInput(ref?: PlRef) {
app.model.data.tableState = createPlDataTableStateV2();
}

// Source-of-truth for session-only dismissal lives at module scope in
// `app.ts` so it survives in-block route changes (Main ↔ Property
// Relationships ↔ Property Distribution). See app.ts for the lifecycle
// notes.
const visibleInfoMessages = computed(() =>
(app.model.outputs.info?.messages ?? []).filter((m) => !dismissedInfoMessages.value.has(m)),
);
function dismissInfoMessage(message: string) {
dismissedInfoMessages.value = new Set(dismissedInfoMessages.value).add(message);
}

const tableSettings = usePlDataTableSettingsV2({
model: () => app.model.outputs.propertiesTable,
});
Expand Down Expand Up @@ -62,9 +73,12 @@ const tableSettings = usePlDataTableSettingsV2({
</template>

<PlAlert
v-for="(message, idx) in app.model.outputs.info?.messages ?? []"
:key="idx"
v-for="message in visibleInfoMessages"
:key="message"
type="info"
closeable
:model-value="true"
@update:model-value="() => dismissInfoMessage(message)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The @update:model-value handler ignores the emitted boolean and calls dismissInfoMessage unconditionally. With :model-value="true" hardcoded, a close click will emit false — but if PlAlert ever emits update:model-value with true (e.g., during internal re-sync or after an animation), every message would be dismissed the moment it mounts. Guarding on !v makes the intent explicit and is safe against any emit direction.

Suggested change
@update:model-value="() => dismissInfoMessage(message)"
@update:model-value="(v) => !v && dismissInfoMessage(message)"
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/pages/MainPage.vue
Line: 81

Comment:
The `@update:model-value` handler ignores the emitted boolean and calls `dismissInfoMessage` unconditionally. With `:model-value="true"` hardcoded, a close click will emit `false` — but if `PlAlert` ever emits `update:model-value` with `true` (e.g., during internal re-sync or after an animation), every message would be dismissed the moment it mounts. Guarding on `!v` makes the intent explicit and is safe against any emit direction.

```suggestion
      @update:model-value="(v) => !v && dismissInfoMessage(message)"
```

How can I resolve this? If you propose a fix, please make it concise.

>
{{ message }}
</PlAlert>
Expand Down
Loading