feat: add todo-mobx-app example with MobX↔FASTElement bridge#7523
Merged
Conversation
Adds examples/todo-mobx-app/, a small Todo app that demonstrates how to bridge a MobX state store into FASTElement template bindings via two example-scoped decorators: - @mobxObservableProperty wraps a getter with Observable.track so FAST treats it as a normal observable property in templates. - @mobxObserver, applied to the class, sets up one mobx.reaction per decorated getter on connectedCallback and disposes them on disconnectedCallback, calling Observable.notify when MobX detects a change. The bridge is uni-directional (MobX -> FAST). UI-local state still uses plain FAST @observable. The example app uses a singleton TodoStore (makeAutoObservable) shared across all components, plus localStorage persistence via mobx.autorun. The <todo-list> repeat directive uses { recycle: false } so per-item MobX reactions are disposed when rows are removed; without this, FAST would rebind existing views to new Todos without tearing down their reactions, leaving stale subscriptions. A DESIGN.md documents the bridge architecture, lifecycle ordering, and known gotchas (volatile getters, useDefineForClassFields, recycle). No existing packages or public API are modified. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…torun Replaces the two custom decorators (@mobxObserver and @mobxObservableProperty) plus the ~180-line src/mobx-integration/ folder with a stock pattern: each component declares the slices of MobX state it renders as FAST @observable fields and uses a single autorun in connectedCallback to mirror values from the MobX store, disposing in disconnectedCallback. Why: - No new abstractions on top of FAST or MobX — readers need to know both libraries' standard APIs and nothing else. - Bridge code shrinks from ~180 lines to 0. - <todo-list> repeat directive no longer needs recycle: false: <todo-item> rebuilds its autorun via FAST's standard `todoChanged` callback when the bound Todo changes on view recycle. - Calling autorun before super.connectedCallback() ensures the mirror fields have correct values when the template first binds, avoiding initial flicker. README and DESIGN.md rewritten to document the new pattern and explain why no custom mechanism is needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…m tokens in todo-mobx-app - Move examples/todo-app/ to examples/csr/todo-app/ (history preserved via git mv). - Move examples/todo-mobx-app/ to examples/csr/todo-mobx-app/ (history preserved). - Group example apps by rendering strategy; the design-system package stays at examples/design-system/ since it is shared across strategies. - Add 'examples/csr/*' to the root package.json workspaces glob. - Wire @microsoft/fast-examples-design-system into todo-mobx-app: import tokens.css once from src/main.ts, declare the module shim, and force data-theme='light' on <html>. - Rewrite every todo-mobx-app *.styles.ts to consume --fast-* tokens (backgrounds, foregrounds, padding, gap, corners, strokes, shadows, font metrics, durations) instead of hard-coded values; accent-color on the native checkbox now resolves to the brand token too. - Update README/DESIGN docs for both apps and the workspace-level examples/README.md and examples/DESIGN.md to reflect the csr/ layout and the design-system integration. - Update .github/copilot-instructions.md example-project entries to the new paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
f4c3162 to
8d83d7b
Compare
chrisdholt
approved these changes
May 21, 2026
This was referenced May 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
📖 Description
Adds a new example,
examples/todo-mobx-app/, that demonstrates how to bridge a MobX state store into FASTElement template bindings.The example introduces two thin, application-scoped decorators that collapse the usual MobX-in-a-web-component boilerplate (a manual
reaction()per observed field + manualObservable.notify()+ manual disposer wiring indisconnectedCallback) down to two annotations:@mobxObservableProperty— marks a getter on a FASTElement subclass as one whose value comes from a MobX-tracked source. The getter is wrapped withObservable.track(this, key)so FAST treats it as a normal observable property in templates.@mobxObserver— class decorator that, onconnectedCallback, sets up one MobXreactionper@mobxObservablePropertykey. When MobX detects a change, the reaction callsObservable.notify(this, key)and FAST re-renders the dependent template bindings. Reactions are disposed ondisconnectedCallback.The bridge is uni-directional (MobX → FAST). Two-way input bindings (e.g.
<todo-form>'s draft text) still use plain FAST@observablebecause that state is UI-local and not part of the MobX store.The example app itself is a small Todo application with the typical features (add / toggle / remove / toggle-all / filter / clear-completed / item count) plus localStorage persistence via a
mobx.autorun. State lives in a singletonTodoStore(makeAutoObservable) and is shared across all components, so no props need to be passed down — components read directly from the store through@mobxObservablePropertygetters.A
DESIGN.mddocuments the bridge architecture, lifecycle ordering, the rationale forrecycle: falseon the<todo-list>repeat directive (otherwise FAST would rebind existing rows to new Todo objects without tearing down their MobX reactions, leaving stale subscriptions), and known gotchas around volatile getters anduseDefineForClassFields.This is an additive example only — no existing packages or public API are modified.
👩💻 Reviewer Notes
examples/todo-mobx-app/src/mobx-integration/. It is intentionally scoped to the example, not promoted to@microsoft/fast-element, so the team can evaluate the pattern before deciding whether to formalize it.npm start -w @microsoft/fast-todo-mobx-app-example(port 9001), then add several todos, toggle some, switch filters, remove an item that was previously in the middle of the list, then toggle a row that took its place — this exercises therecycle: falsepath that ensures per-item MobX reactions are torn down and rebuilt when rows shift.examples/todo-mobx-app/tsconfig.jsonsetsuseDefineForClassFields: falseandskipLibCheck: true. The first is required so MobX'smakeAutoObservable(this)sees constructor-assigned fields (rather thanObject.defineProperty-defined fields) and can convert them to observables. The second sidesteps a known gap inmobx@6.13.5'sObservableMaplib types vs. TypeScript'sesnextMapinterface.📑 Test Plan
npm run build(full repo): passes; the new package builds in ~1s viatsgo -p tsconfig.json && vite buildand emits a ~131 KBwww/bundle.npm run biome:checkon the new files: clean.npm run checkchange: no change files required (the example package is"private": true, so beachball skips it).npm start -w @microsoft/fast-todo-mobx-app-example: add 3 todos through the form (verifies action propagation), verify list/stats text update reactively, toggle the middle row (verifies per-item bridge), switch the filter to "completed" (verifies computed view), remove an item and toggle the row that shifted into its slot (verifiesrecycle: falseprevents stale reactions), reload the page (verifies localStorage persistence). All scenarios behaved as expected.examples/todo-app/, which also has no tests of its own.✅ Checklist
General
$ npm run change⏭ Next Steps
If the team likes the pattern, a natural follow-up is to promote
mobx-integration/into its own optional adapter package (e.g.@microsoft/fast-element-mobx) so that other apps can consume the decorators without copy-pasting them.