chore: migrate to new column-access mechanism (SDK 1.81.x) - #167
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates @platforma-sdk to 1.80.x and migrates the block model to the new host-driven column-access mechanism, replacing the old ColumnCollectionBuilder and ColumnMatch APIs with ColumnsCollection and ColumnRecipe. The review feedback correctly identifies a critical bug where ColumnsCollection is instantiated without sources, which would break main table rendering, and notes the accidental removal of the assemblingKabatPf resolver. Additionally, a defensive programming improvement was suggested to safely access axesSpec[0]?.name to prevent potential TypeErrors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…lumn-access mechanism - Updated versions for @platforma-sdk/workflow-tengo, @platforma-sdk/model, @platforma-sdk/ui-vue, @platforma-sdk/test, and @milaboratories/helpers. - Migrated the model to the new column-access mechanism, replacing old APIs with host-driven alternatives.
Carries the 1.80.x column-access migration forward to 1.81.
Renames and signature changes:
- `isColumnLazy` → `isDataColumn`, extracted alongside the accessor adapter
into a shared `toGraphColumns` used by every PColumn-only consumer.
- The `linker` label formatter now receives `LinkerParts` instead of a flat
label array; the chain is rendered from `parts.linkers[].text`.
Completes two spots the 1.80 pass left on deprecated APIs:
- `umapPf` / `umapPcols` move off `ctx.resultPool.getAnchoredPColumns` and
`TreeNodeAccessor.getPColumns()` onto anchored `ColumnsCollection`
discovery. The legacy `{ anchor: "main", idx: 1 }` axis binding has no
selector form, so the clonotype axis name is read off the resolved anchor
spec and matched by name.
- Every `PlRef` → spec read goes through `getSpecByRef`, which absorbs the
`ColumnAbsentError` that `Column(ref)` throws once the pool is locked
without the column. `ctx.resultPool.getPColumnSpecByRef` returned
`undefined` there, and an uncaught throw fails the whole output.
`ctx.resultPool.getOptions` stays in `clusterColumnOptions`: the option is
persisted as `diversificationColumn` and feeds `addAnchor`, which needs a
`PlRef`. Reason recorded at the call site.
No `keepMatchingRules`-style workaround is needed: the `evaluateRules` bug
that discarded every display rule when one matched nothing is fixed in
@platforma-sdk/model 1.81.1.
The migration started sending `recipe.id` — a full `ColumnUniversalId` — into
`filters[].value.column` / `rankingOrder[].value.column`. The workflow cannot
read that shape: `bundleBuilder.addSingle` branches on `__isRef`, `resolvePath`
and `{source, axisFilters}`, and drops everything else into the anchored-query
path. A `ColumnDiscoveredId`, which is what a linker-reached hit carries, is not
a valid `AnchoredPColumnSelector`, so only zero-hop hits would have resolved.
Restore the pre-migration contract while keeping the new API inside the model:
- `matchToColumnId` reduces the recipe id with `extractPObjectId`. That is the
same global ref id the old `ColumnMatch.column.id` carried, so persisted
selections and the workflow keep working with no changes on their side.
- `dedupByLeafId` collapses discovery results to one recipe per storage column
for the filter/ranking lists. The old `findColumns` did this itself, reducing
hits into a `Map<PObjectId, ColumnMatch>` and merging reachability variants;
`discover().getColumns()` returns one recipe per variant instead, which with
leaf ids on the wire would surface as duplicate options sharing one value.
The table keeps the full recipes — their distinct ids are what V3 joins on.
This is deliberate scaffolding, confined to those two functions in
`model/src/util.ts`. `docs/handoff-addsingle-discovered-ids.md` specifies the
workflow-tengo support needed to remove it and put the full id on the wire.
`anchorName` was always the literal "main". It only carried meaning while the wire value was an anchored query id and the workflow had to know which anchor to resolve it against; with a leaf `PObjectId` resolved by ref, no anchor is involved. Nothing in the workflow reads the field, and the two UI lookups that appeared to filter on it — `options.find((o) => o.value?.anchorName === "main")` — were picking the first entry of a list where every entry carried the same value. They now say so directly. `anchorRef` stays: the UI compares it against the current anchor to tell a freshly arrived filter/ranking config from a stale one. Also removes the unused `ScopedColumn` and `Column` types. This changes the shape of `BlockArgs`, so existing projects re-run the workflow once. Stored `BlockData` keeps the field as an inert leftover; no migration is needed to read it. Drops the handoff note added in the previous commit — it had served its purpose; the requirement it captured is summarised in the changeset instead.
ac8ee56 to
dcfdbab
Compare
`getSpecByRef` wrapped `Column(ref)` in a try/catch to turn `ColumnAbsentError` into `undefined`. `DataColumn.getStatusByPlRef` answers the same question directly, so the swallow becomes an explicit guard and exceptions stay out of the control flow. The guard covers only the `absent` status — `Column(ref)` already returns `undefined` while a column is still resolving. Behaviour is unchanged: a missing column is silently ignored. That is deliberate for this block, which works with whatever the upstream graph provides. Despite what the SDK's note on the error suggests, `absent` is not a definitive "gone for good" — it occurs in normal configurations, so it must not drive any user-facing warning.
ca5a9c5 to
1c0d79d
Compare
What
Update
@platforma-sdkto 1.80.x and migrate the block model off the removed column-access APIs onto the new host-drivenColumnsCollection/ColumnRecipemechanism.Why
ColumnCollectionBuilder/ColumnMatch/ArrayColumnProviderwere removed in the SDK. The new API discovers and filters columns host-side and fetches specs on demand (getSpec()) instead of pulling every upstream spec eagerly into the 8 MB model sandbox.Key changes (model)
ColumnCollectionBuilder+findColumns→ColumnsCollection(["result_pool"]).discover(...);ColumnMatch→ColumnRecipe(.column.spec/.column.id→.getSpec()/.id).resultPool.getPColumnSpecByRef(ref)→Column(ref).getSpec().include/excludeselectors wherever expressible (linkers, label, cluster-id names, per-sample axis,type: "String",hideDataFromUi/Graphs); only non-expressible predicates (File type, "produced-by-lead-selection" last-trace-step) remain as JS post-filters.createPlDataTableV3display rules moved from(spec) => booleanlambdas to host-sideColumnSelectors.Annotation.*constants where available.@milaboratories/helpersbumped 1.14.2 → 1.14.4 to match the SDK (fixes aTS2742declaration-portability error).No user-facing behavior change intended.
Validation
type-check,lint,format,buildgreen across model / ui / workflow / test (no unit/integration tests exist in the block).rankcolumn, workflowbundleBuilder.addSingleid compatibility (incl. persisted filter/ranking ids), and umap/pf pframe assembly.Greptile Summary
Migrates the block model from the removed
ColumnCollectionBuilder/ColumnMatch/ArrayColumnProviderAPIs to the new host-drivenColumnsCollection+ColumnRecipemechanism introduced in SDK 1.81.x, with the main table's(spec) => booleandisplay lambdas moved to host-sideRelaxedColumnSelectors. No user-facing behavior change is intended.Key touched terms:
ColumnsCollectionColumnCollectionBuilder+AnchoredColumnCollection; now called with["result_pool"]or aTreeNodeAccessor[]instead of constructing a builderColumnRecipe.discover().getColumns(); accessed via.getSpec(),.getData(),.idColumnMatch;.column.spec/.column.idaccess replaced by.getSpec()/.idDataColumnColumnRecipefor bare leaf storage columns; also providesDataColumn.getStatusByPlRef()isDataColumn()to filter recipes forcreatePFrameForGraphs; status check replaces try/catch onColumnAbsentErrorScopedColumnIdBlockArgsfor filter/ranking selectionsanchorName: stringremoved (was always"main", never read by the workflow);columntype staysPObjectIdviaextractPObjectIdColumnsMeta.allMatches/scoresColumnMatch[]→ColumnRecipe[]getSpecByRefColumn(ref)?.getSpec()with anabsentstatus guardctx.resultPool.getPColumnSpecByRef(ref)calls across the modeldiscoveryExcludeSelectorscommonExcludeSelectors; adds label, cluster-id, and per-sample axis excludes pushed host-sidededupByLeafIdfindColumns()did internally; required becausediscover().getColumns()returns one recipe per reachability variantbuildCollectionctx: RenderCtxargument (relies on SDK ambient context); now exposesanchorSpecdirectly; returnsColumnsCollection(["result_pool"])instead of anAnchoredColumnCollectionBlockArgsshape change:ScopedColumnId.anchorNamedropped → existing projects re-run the workflow once on next open; storedBlockDatakeeps the field as an inert leftover.ColumnsCollection()no-arg scope used forpoolDiscoveredin the main table output vsColumnsCollection(["result_pool"])everywhere else, and the missingFilevalue-type guard inisSelectableMatch.bundleBuilder.addSingleid compatibility, umap/pf pframe assembly).Confidence Score: 5/5
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[RenderCtx output evaluation] --> B[buildCollection] B --> C["ColumnsCollection(['result_pool'])"] C --> D["discover({ anchors, mode: 'related', maxHops: 2 })"] D --> E[dedupByLeafId] E --> F[isSelectableMatch] F --> G[allMatches / scores] G --> H[computeDefaultFilters] G --> I[computePresets] A --> J[filterConfig output] J --> K["result.collection.discover({ anchors, exclude: discoveryExcludeSelectors })"] K --> L[dedupByLeafId] L --> M[isSelectableMatch] M --> N[filterableMatches → options] A --> O[rankingConfig output] O --> P["result.collection.discover({ anchors, exclude: [..., type:'String'] })"] P --> Q[dedupByLeafId] Q --> R[isSelectableMatch] R --> S[rankableMatches → options] A --> T[table output] T --> U["ColumnsCollection() — no-arg"] U --> V["discover({ anchors: leadSelectionSpec })"] V --> W[filter isProducedByLeadSelection] W --> X[primaryColumns / secondaryColumns] X --> Y[createPlDataTableV3] A --> Z[umapPf / umapPcols] Z --> AA["ColumnsCollection(['result_pool']).discover(umap name pattern)"] AA --> AB[toGraphColumns → createPFrameForGraphs]Reviews (2): Last reviewed commit: "refactor: guard on column status instead..." | Re-trigger Greptile
Context used: