Skip to content

Catalogue the SingleCellExperiments a session can switch between - #58

Open
david-priest wants to merge 2 commits into
mainfrom
feat/switch-between-sces
Open

david-priest wants to merge 2 commits into
mainfrom
feat/switch-between-sces

Conversation

@david-priest

Copy link
Copy Markdown
Owner

Replaces #57, which closed when the branch it was based on was deleted after merging. The commit is unchanged, and it merges into main without conflicts.

Stacked on #56 so the diff reads clean; retarget to main once that merges.

R side only. The React dataset picker that consumes availableDatasets is not here yet.

Why

GateLabR binds to one object at launch. The GateLabR-specific Shiny interface let a user move between the SCEs already in their session, and that went missing when the React interface replaced it in 1.4.0 (launchLegacyGateLabR records the retirement).

The host contract never lost the ability — its datasets field is a list and GateLabHostDatasetPort.listDatasets() returns an array. Only the launcher and the app collapsed it to one.

What this adds

file change
R/sce_catalogue.R list SCEs in an environment and describe them cheaply
R/host_bridge.R manifest carries availableDatasets beside datasets
R/launch_react.R an activate-dataset request swaps sce_state() and resends

Listing is deliberately cheap — dimensions, assay names, and whether a workspace is already stored in metadata(), never assay data. The expensive part, the per-sample binary resources, is still registered for one object at a time and re-registered on a switch.

Two refusals worth reviewing

  • Activation refuses a name that is not a SingleCellExperiment, and uses inherits = FALSE so a name in an attached package cannot masquerade as a session object — activating one would write results somewhere the user cannot see.
  • Switching is refused unless the request says the workspace has been saved. The whole workspace — gates, populations, scales, compensation provenance — lives in metadata(sce), so switching without a save loses it, and R cannot verify the app saved. An explicit acknowledgement turns a silent loss into an error. If you would rather R flushed it itself, say so; that is a different and arguably better design.

Tests

test-sce-catalogue.R covers enumeration (non-SCEs excluded), catalogue contents, the object launched from an expression with no name in the environment, and the activation refusals.

⚠️ Not run — the agent does not execute R here. Please run devtools::test(filter="sce-catalogue") before merging.

🤖 Generated with Claude Code

david-priest and others added 2 commits September 10, 2026 10:30
GateLabR binds to one object at launch. The GateLabR-specific Shiny interface
let a user move between the SCEs already in their session; that went missing
when the React interface replaced it in 1.4.0.

The host contract never lost the ability -- its datasets field is a list and
GateLabHostDatasetPort.listDatasets() returns an array. Only the launcher and
the app collapsed it to one.

  R/sce_catalogue.R   list SCEs in an environment and describe them cheaply
  host_bridge.R       manifest carries availableDatasets alongside datasets
  launch_react.R      an activate-dataset request swaps sce_state and resends

Listing is deliberately cheap: dimensions, assay names and whether a workspace
is already stored in metadata(), never assay data. The expensive part, the
per-sample binary resources, is still registered for one object at a time and
re-registered on a switch.

Activation refuses a name that is not an SCE, and refuses to switch unless the
request says the current workspace has been saved. The whole workspace lives in
metadata(sce), so switching without a save loses it, and R cannot check that
for itself -- an explicit acknowledgement turns a silent loss into an error.

The React dataset picker that consumes availableDatasets is not in this commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…are per object, the reply reaches the browser

The first cut of the catalogue switched sce_state but left everything else on
the launch object. Every write went to the launch-time name, so after a switch
the second object overwrote the first in the global environment; the dataset
id and the resource names never changed, so the first object's workspace and
memberships could be written into the second and its resource URLs served the
second's data; the activate reply nested `ok` inside `result`, which the
browser's response parser discards, so every switch timed out after 30 s; the
state was swapped before the manifest was built, so a failure left the session
on an object it could not describe; a running compensation Apply committed its
matrix into whichever object was loaded when it finished; and the switched
object never got the precompensation note the launch prints.

active_name now lives beside sce_state at app level and every write, request
and job uses it; each object has its own dataset id (.gatelabr_dataset_id_for)
and therefore its own resource names; the reply carries ok at the top level;
the manifest is built for the replacement before the state swaps; a switch is
refused while an Apply is running; the switch prints the writeback name and
the precompensation note. The catalogue marks objects the descriptor would
refuse, with the reason, and recognises a workspace saved in the older plain
JSON form.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@david-priest

Copy link
Copy Markdown
Owner Author

Second commit (89f918c) reworks the switch after the 2026-09-11 review found the first cut switched sce_state and nothing else:

  • active_name now lives beside sce_state at app level (so it survives a reload), and every write — the generic request handler, the compensation job — goes to active_name() rather than the launch-time name. Before, B overwrote A in the global environment after a switch.
  • Each object has its own dataset id (.gatelabr_dataset_id_for) and therefore its own resource names, so A's workspace and memberships cannot be written into B and A's resource URLs cannot serve B's data.
  • The activate reply carries ok at the top level, where the browser's response parser reads it; the nested form was discarded and every switch timed out after 30 s.
  • The manifest is built for the replacement before the state swaps, so a refused object leaves the session where it was.
  • A switch is refused while a compensation Apply is running, which would otherwise commit its matrix into the newly loaded object.
  • The switch prints the writeback name and the precompensation note, as the launch does.
  • The catalogue marks objects the descriptor would refuse (no assays, no events, missing sample column) with the reason, and recognises a workspace saved in the older plain-JSON form.

GateLab has no picker for the catalogue yet — listDatasets() is in the host contract but nothing sends activate-dataset — so this stays R-only until that is built.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant