@tenmol/stores
Client state for the PyMOL web client. ~2,950 lines of plain TypeScript, one dependency (@tenmol/protocol, types only), no framework and no state library.
Importing
The barrel (src/index.ts) exports createStore, bridgeBinding, poll,
connection, feedback, objects, objectsSource and ui. It is frozen
— console.ts and settings.ts are deliberately not in it, and neither is
anything you add. Reach a store by subpath:
package.json ("./*": "./src/*.ts") and tsconfig.base.json
("@tenmol/stores/*") already resolve that, so adding src/<yourStore>.ts is
the entire installation step and no shared file is edited.
Three things to know before you change anything here
1. Nothing is optimistic exceptui.ts. Every PyMOL mutation is
round-tripped. A settings write can silently no-op at the wrong level,
SettingGenerateSideEffects can invalidate geometry, and the object panel has
no push feed at all — so the truth is always the next poll or the next topic
event, never what the UI just asked for. ui.ts is exempt because none of it is
PyMOL state.
2. Gap detection in bridgeBinding.ts is currently INERT, on purpose.
EventMessage.seq is monotonic per topic per connection, but
packages/client/src/connection.ts forwards message.payload and drops
message.seq on the floor. So every binding runs with seq === undefined and
reports seqAvailable: false rather than pretending everything is fine. The
moment the client forwards seq, bind() starts working with no change here.
3. No React in this package. The binding is apps/web/src/app/hooks.ts.
Everything here is therefore testable under vitest’s node environment with no
DOM — which is the reason the split exists.
Tests
console.ts and
settings.ts are tested from the app, in
apps/web/src/features/console/** and apps/web/src/features/settings/**
(214 tests between them), because their tests exercise the store together with
the feature that wires it. Changing either of those two files and running only
--filter @tenmol/stores will tell you nothing; run pnpm test.