Repository navigation
Feat(app): navigating guide view page to objective / subobjective - #529
Open
Aditya-Dawadikar wants to merge 4 commits into
Open
Aditya-Dawadikar wants to merge 4 commits into
Aditya-Dawadikar wants to merge 4 commits into
Conversation
…ading
Going back from a guide opened in an objective reset the page to the graph and the default sub-objective at the top, so the learner had to find their place again. The objective page now remembers where they
were, both for browser back and for the breadcrumb.
Paths to trace:
1. Objective page state lives in the URL
routes/objectives/$slug/index.tsx
- validateSearch: parseObjectivePageSearch -> { view?: "linear", target?: <target node id> };
the graph view and the default target are left out, so a plain visit keeps a bare URL.
- The view and selection are seeded from the URL on mount
(useObjectiveSelection(snapshot, { targetId: search.target })), then
a useEffect writes them back with navigate({ replace: true,
resetScroll: false }). A target already named in the URL is kept as
written, so arriving never triggers a navigate of its own.
- shouldReload: cause !== "stay" stops these URL writes from
refetching the objective; entering the page still loads it.
-> Browser back restores the view and sub-objective, and the router's
scrollRestoration restores the scroll position.
2. Breadcrumb from the guide reader lands on the exact step
routes/guides/$slug/index.tsx -> lib/breadcrumbs.ts
- The guide URL already carries ?objective=&target= (from
SubObjectiveStep / WalkthroughPanel). With a sequence, the objective
crumb gets:
search: { view: "linear", target: sequence.targetId } when
search.target is set (opened from the linear view),
otherwise nothing (opened from the graph)
hash: guideAnchor(slug) -> "guide-<slug>"
- BreadcrumbOrigin / Breadcrumb now take optional search and hash;
buildBreadcrumbs passes them through, and the reader's crumb <Link>
uses them.
3. Scrolling to the guide (linear view)
components/objective/SubObjectiveStep.tsx
- Each guide step that can be opened gets id={guideAnchor(slug)}, plus
scroll-mt-24 so the sticky navbar does not cover it.
- Uses the router's defaultHashScrollIntoView (router.tsx): no custom
scroll code.
4. Reopening the guide (graph view)
routes/objectives/$slug/index.tsx
- guideSlugFromAnchor(location.hash) finds the guide; a mount-only
useEffect selects its node, which opens the panel on it. Browsers
never send the hash to the server, so this waits until after
hydration; seeding it into the first render caused a hydration
mismatch.
5. Selection hook accepts a starting point
components/objective/ObjectiveGraph.tsx
- useObjectiveSelection(snapshot, initial?) seeds selectedId and
targetId; an id that is no longer a target falls back to
defaultTargetId as before. Existing callers (preview, revisions) are
unchanged.
6. Fix: target listed twice in a sub-objective
lib/objectiveSnapshot.ts
- buildSubObjectives skips the target's own row when a stored sequence
already ends with the target, since it appends the target once. This
stops the duplicate step, React duplicate-key errors and a duplicate
anchor id in the linear view, the contribution preview and revisions.
The dedupe that worked around this in findGuideNeighbours is
removed.
- New helpers: ObjectivePageSearch, parseObjectivePageSearch,
guideAnchor, guideSlugFromAnchor.
7. Seed data for manual verification
supabase/seed.sql
- "Make a 2D Platformer" (make-a-2d-platformer) reuses the Game
Development guides across three sub-objectives (first is featured);
"Sprites and Assets" sits only in the 2nd and 3rd, which reproduces
the case in the issue.
lib/__tests__/objectiveSnapshot.test.ts - buildSubObjectives: lists the target once when the stored sequence already ends with it (the duplicate-target fix). - parseObjectivePageSearch: keeps view=linear and the target; leaves out the default graph view; drops values that are missing or malformed. - guideAnchor / guideSlugFromAnchor: round-trip a slug; a hash that names no guide gives undefined. lib/__tests__/breadcrumbs.test.ts - buildBreadcrumbs passes an origin's search and hash through to the crumb, which the guide reader's link to the objective relies on. components/objective/__tests__/SubObjectiveStep.test.tsx - Only openable guides get the guide-<slug> anchor id that the router scrolls to; requests get none. components/objective/__tests__/ObjectiveGraph.test.tsx - useObjectiveSelection's starting point, through the two-target Reader harness (now takes initialView and initial): - reopens the linear view on the sub-objective it left - reopens the graph with the returned guide selected - falls back to the default for a target no longer in the objective
Aditya-Dawadikar
marked this pull request as ready for review
October 21, 2026 18:02
Aditya-Dawadikar
requested review from
andyrodrigues30 and
tonylam0
as code owners
October 21, 2026 18:02
Member
|
LGTM! |
kevin8999
approved these changes
Oct 31, 2026
This branch has not been deployed
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.
Summary
Closes #515.
Going back from a guide opened in an objective sent the learner to the graph view and the first sub-objective, at the top of the page. They had to pick the sub-objective again and scroll back down. Now the objective page remembers where they were:
?view=linear&target=<id>), and defaults are left out so a plain visit keeps a bare URL. Changes replace the current history entry, andshouldReloadkeeps those URL updates from refetching the objective.?objective=&target=, which drives both Prev/Next and the breadcrumb. The breadcrumb links to/objectives/<slug>?view=linear&target=<id>#guide-<slug>.id="guide-<slug>", so the router's built-in hash scrolling brings it into view. There's no custom scroll code.#guide-<slug>hash and opens that node. This happens after the page loads in the browser, since the server never sees the hash.Builds on top of the PR #517
Type of change
Verification
pnpm -r typecheckpassespnpm -r buildpasses