feat: Guided Tours Ephemeral Pipelines#2334
Conversation
🎩 PreviewA preview build has been created at: |
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
ab7dd0b to
8eb1ea3
Compare
fb58c22 to
a71461e
Compare
151abae to
3b86f76
Compare
774e9d6 to
f3ba10d
Compare
802ddf3 to
fed719d
Compare
f3ba10d to
6f56eba
Compare
fed719d to
288fe40
Compare
6f56eba to
07be379
Compare
4479f54 to
e7bee79
Compare
d009f6a to
7dd3ef7
Compare
e223594 to
d640e0a
Compare
cdf8d07 to
d542c2d
Compare
b83942f to
a52076f
Compare
| } | ||
|
|
||
| return ( | ||
| <TourPipelineStorageProvider> |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
Wrapping the entire tour editor in TourPipelineStorageProvider makes every existing File menu action use the new session-storage-backed service. Actions such as File → Save as and File → New pipeline create a sessionStorage-only PipelineFile and then navigate to APP_ROUTES.EDITOR_V2_PIPELINE. That route is outside this provider and resolves fileId through the normal IndexedDB-backed PipelineStorageProvider, where the session-only id has no registry entry, so the editor opens on a not-found/error path after showing a successful action.
Suggestion: Either hide/disable the normal File menu actions that leave tour mode while a tour is active, or route those actions through an explicit promotion path that writes to the normal pipeline storage before navigating (the same separation used for tour completion save/explore). Add a guard so session-storage ids are never passed to the normal editor route.
Rule: Tangle UI review checklist: General quality — proper error handling and holistic architecture; changed code must not introduce broken user flows.
| if (isLoadingUserPipelines) return null; | ||
| const normalized = name.trim().toLowerCase(); | ||
| if (normalized === "") return "Name cannot be empty"; | ||
| const excluded = new Set( |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
The dialog now removes excludeNames from the duplicate-name set using trimmed/lowercased values, but the rename callers still disable submit with raw equality checks such as name === pipelineNameFromSpec. Entering the current name with leading/trailing whitespace avoids the duplicate error, is not disabled, then handleSubmit trims it back to the existing name and the underlying rename path throws because that file already exists.
Suggestion: Normalize once inside PipelineNameDialog and pass the trimmed name into isSubmitDisabled, or make the callers compare name.trim().toLowerCase() against the excluded/current name. Keep the duplicate guard and submit-disabled logic aligned on the same normalized value.
Rule: Tangle UI review checklist: TypeScript/General quality — prefer type-safe validation over downstream runtime errors; user-facing dialogs should prevent invalid submissions before invoking storage mutations.

Description
Moves tour pipelines out of the durable IndexedDB store and into session storage, so they are truly ephemeral. Before this change, a tour-pipeline lived alongside the user's real pipelines: it showed up in the pipelines list, contended for unique names, and survived browser refresh — none of which is what we want from a guided demo.
The fix is a scoped storage override that only applies while the user is on
/tour/$tourId. Inside the tour route,usePipelineStorage()returns aTourPipelineStorageServicebacked bysessionStorage; everywhere else, it still returns the durable IndexedDB service. The autosave path, the spec loader, and the editor's storage consumers don't need to know which one they're talking to.What changed
New scoped storage layer (
src/providers/TourProvider/tourPipelineStorage/)SessionStoragePipelineDriver.ts— driver that reads/writes pipeline YAML againstsessionStoragekeys, matching the shape the existingPipelineStorageServiceexpects from a driver.TourPipelineFolder.ts— single root folder that holds the tour pipeline, withcanMoveFilesOut: falseetc. so the rest of the app can't accidentally relocate it.TourPipelineStorageService.ts—PipelineStorageServicesubclass whoserootFolder,resolvePipelineByName, etc. all go through the session-storage driver.TourPipelineStorageProvider.tsx— context override that the tour route wraps its body in. Inside,usePipelineStorage()resolves to the tour service.resetAllTourPipelineState.ts— clears every__tour__*key from session storage and evicts matching entries from the react-query spec cache. Called when starting any tour so each run begins clean.Tour route plumbing (
src/routes/Dashboard/Learn/Tour.tsx)TourPageis split intoTourPage(resolves the tour, guards on!tour) andTourPageBody(does the pipeline resolution and rendering). The split exists soTourPipelineStorageProvidercan wrap the body —usePipelineStorage()insideTourPageBodythen resolves to the tour storage.createTourPipelineis renamedfindOrCreateTourPipelineto reflect that browser refresh mid-tour reuses the existing session-storage file (so the tour resumes on the same step with edits intact). Re-entering the tour from the Learn page still starts fresh —FeaturedTours/ToursLibrarycallresetAllTourPipelineStatebefore navigating, which wipes session storage and the react-query spec cache.Pipeline registry orphan fix (
src/services/pipelineService.ts)deletePipelineremoved the file from its folder driver but didn't delete the corresponding entry from the pipeline registry. The orphaned registry entry caused later "name already exists" errors for tour pipelines whose files had been cleared but whose registry rows lived on. Now both are deleted.Name-dialog race fix (
src/components/shared/Dialogs/PipelineNameDialog.tsx)setErroreffect that didn't always settle before the user clicked Submit, occasionally letting through a name that should have been rejected. Rewrote it as auseMemoso the error is always in sync with the current input. Also addedexcludeNamesso a rename-to-self in the regular editor doesn't flag itself as a collision.Why scoped override rather than a flag
Threading a
isTour: trueflag through every storage call would have leaked tour-awareness into a layer that doesn't need it. The provider override is opaque to consumers — sameusePipelineStorage(), samePipelineFileshape, different backing store.Related Issue and Pull requests
Progresses https://github.com/Shopify/oasis-frontend/issues/583
Type of Change
Checklist
Test Instructions
Pre-req: at least one registered tour exists (so use this branch with #2306 / #2312 on top, or open a Storybook-style local entry).
Ephemerality
?step=N) and the edit you made is still there (session storage survives same-tab refresh, as intended).Name-collision regression
/learn/tours. No "Name already exists" error —resetAllTourPipelineStateclears the prior run.Registry orphan regression
Regression