Skip to content

fix(ui): stop the site-wide g-shortcut handler from racing app-shell nav#6959

Merged
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
galuis116:fix/g-prefix-shortcut-collision
Jul 17, 2026
Merged

fix(ui): stop the site-wide g-shortcut handler from racing app-shell nav#6959
loopover-orb[bot] merged 1 commit into
JSONbored:mainfrom
galuis116:fix/g-prefix-shortcut-collision

Conversation

@galuis116

Copy link
Copy Markdown
Contributor

Summary

  • app-shell.tsx's document-level "g " handler (o/w/r/p/a -> SPA navigate()) and keyboard-shortcuts.tsx's window-level "g " handler (h/d/a/r -> window.location.assign()) are both always mounted on every /app/* page. Pressing g then r or g then a fired both: the SPA navigation from app-shell.tsx and a hard, full-page navigation from keyboard-shortcuts.tsx, aborting the SPA transition and taking the user out of the app entirely.
  • Fixed by making the site-wide handler defer entirely to app-shell.tsx while an /app/* route is mounted (checked via useLocation().pathname), so only one handler ever resolves a given "g " sequence.
  • KeyboardShortcutsDialog's advertised shortcut list now also swaps to the in-app destinations (g o/g w/g r/g p/g a) while on /app/*, so the cheat sheet stays accurate instead of advertising bindings that no longer apply.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves.

Closes #6811

Validation

  • git diff --check
  • npm run actionlint (backend-only check; this PR only touches apps/loopover-ui)
  • npm run typecheck (via npm run ui:typecheck, the UI-scoped equivalent)
  • npm run test:coverage (backend-only; this PR only touches apps/loopover-ui, which is outside the Codecov patch gate per codecov.yml's ignore: apps/**)
  • npm run test:workers (backend-only; not applicable to this UI-only change)
  • npm run build:mcp (not applicable to this UI-only change)
  • npm run test:mcp-pack (not applicable to this UI-only change)
  • npm run ui:openapi:check (no API/schema changes in this PR)
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate (no dependency changes in this PR)
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

If any required check was skipped, explain why:

  • This PR only touches apps/loopover-ui/src/components/site/keyboard-shortcuts.tsx (plus its test file). The backend-only checks above (actionlint, test:coverage, test:workers, build:mcp, test:mcp-pack) and dependency/API checks (npm audit, ui:openapi:check) have no surface to exercise for a change scoped entirely to one UI event handler, so they were not run standalone; npm run ui:lint, npm run ui:typecheck, and npm run ui:build all pass locally, and the new keyboard-shortcuts.test.tsx suite (5 tests) covers both collision keystrokes plus the advertised-shortcut-list swap.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. (N/A -- no auth/cookie/CORS/session changes in this PR.)
  • API/OpenAPI/MCP behavior is updated and tested where needed. (N/A -- no API/OpenAPI/MCP changes in this PR.)
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks. (N/A -- this change is a client-side keyboard event handler, not API-data-driven.)
  • Visible UI changes include a UI Evidence section below with JPG/JPEG or PNG screenshots. (N/A -- this fix changes keyboard event-handling logic and the shortcut cheat-sheet's text list only; there is no static visual difference to screenshot, and the interaction difference -- one navigation instead of two racing navigations -- is exercised by the new automated tests instead.)
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs.

Notes

  • No UI Evidence table: this is a behavioral fix to keyboard event handling (which handler wins a given keystroke) and to the shortcut cheat-sheet's advertised text, not a static visual change -- there is no at-rest pixel difference to capture. The regression is instead pinned by keyboard-shortcuts.test.tsx, asserting window.location.assign is no longer called for g r / g a while an /app/* route is mounted, and that the cheat sheet's advertised list matches whichever handler is actually live.

…nav (JSONbored#6811)

app-shell.tsx's own g-prefix handler owns SPA navigation on /app/* routes, but the site-wide handler in keyboard-shortcuts.tsx fired unconditionally on the same keystrokes and hard-navigated away from the app via window.location.assign, colliding on g r and g a. The site-wide handler now defers to AppShell while an /app/* route is mounted, and the advertised shortcut list swaps to the in-app destinations so it stays accurate.
@galuis116
galuis116 requested a review from JSONbored as a code owner July 17, 2026 15:38
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 17, 2026
@loopover-orb

loopover-orb Bot commented Jul 17, 2026

Copy link
Copy Markdown

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-17 15:43:59 UTC

2 files · 1 AI reviewer · no blockers · readiness 98/100 · CI green · unknown

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This fix makes the site-wide 'g <key>' handler defer to app-shell.tsx while any /app/* route is mounted, resolving the race where both a hard `window.location.assign` and an SPA `navigate()` fired on the same keystroke. It also correctly swaps the advertised shortcut list in the cheat sheet to match whichever handler is actually live, and the added tests exercise the real keydown/route-path combinations rather than fabricating state. The change is narrow, directly targets the linked issue, and the guard placement (`if (isAppRoute) return;` before entering the 'g' pending-state machine) is in the right layer — it stops the handler at its source rather than patching a downstream collision.

Nits — 5 non-blocking
  • The PR doesn't show the app-shell.tsx side of the fix, so I can't verify from this diff alone that app-shell.tsx's own handler and this one now agree on which keys map to which routes outside of what's asserted in the tests — worth double-checking in review that h/d/a/r semantics don't quietly diverge from the g o/w/r/p/a map.
  • `buildGroups` is called fresh on every render of `KeyboardShortcutsDialog` (keyboard-shortcuts.tsx:78) — for two static arrays this is inexpensive, but consider memoizing with `useMemo(() => buildGroups(isAppRoute), [isAppRoute])` if this pattern gets reused elsewhere with heavier arrays.
  • The mocked `useLocation` in the test file only ever returns `{ pathname }`, so if `KeyboardShortcutsDialog` or a future change reads other fields off the location object the mock will silently return `undefined` for them — low risk today but worth a comment.
  • Confirm app-shell.tsx's route matching also uses `pathname.startsWith("/app")` (not an exact-match or different prefix) so the two handlers' mount conditions stay in lockstep as new /app/* subroutes are added.
  • Consider extracting the `isAppRoute` computation into a small shared helper if app-shell.tsx duplicates this exact `startsWith("/app")` check, to avoid the two handlers drifting on the boundary condition independently in the future.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #6811
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 1952 registered-repo PR(s), 1282 merged, 52 issue(s).
Contributor context ✅ Confirmed Gittensor contributor galuis116; Gittensor profile; 1952 PR(s), 52 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Review context
  • Author: galuis116
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 1952 PR(s), 52 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Keep the PR focused and include validation evidence before maintainer review.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
/ mobile before / (mobile)
before / (mobile)
after / (mobile)
after / (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit 39c3738 into JSONbored:main Jul 17, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Colliding global "g"-prefix keyboard shortcuts between site-wide and in-app dashboard

2 participants