Skip to content

refactor: apply url actions in the test route (@miodec) - #8475

Merged
Miodec merged 180 commits into
masterfrom
refactor/url-handler-router
Oct 19, 2026
Merged

Miodec merged 180 commits into
masterfrom
refactor/url-handler-router

Conversation

@Miodec

@Miodec Miodec commented Oct 18, 2026

Copy link
Copy Markdown
Member

Description

Stacked on #8459. Folds controllers/url-handler.tsx into the router.

before: authEvent "authStateChanged" -> await loadPromise -> read window.location -> apply (every auth change, any path)
after:  test route beforeLoad (after userData beforeLoad) -> apply -> redirect to / without the action params (replace)
  • controllers/url-handler.tsx -> router/url-actions.ts. Loaders take the value instead of reading window.location; applyUrlActions(location) is the single entry point and returns the search/hash left over.
  • Test route beforeLoad applies testSettings, customTheme, challenge and the discord oauth hash (/verify already redirects to / keeping the hash), then redirects to drop them. Skipped on preload.
  • Values are read from location.searchStr via findGetParameter, not the parsed search: URLSearchParams turns the + of base64 (customTheme) and lz (testSettings) into spaces.
  • Side-effect import removed from index.tsx.

Behaviour changes:

  • Params are applied once and removed from the url, so they're no longer re-applied on a later sign in/out or reload.
  • Only handled on / (all generated links point there; discord goes through /verify).
  • Discord: only the hash is dropped; previously history.replaceState(null, "", "/") also dropped the search.

Evidence

  • url-actions.spec.ts: existing test settings cases moved over, plus applyUrlActions cases (no actions, rest kept, raw + kept, discord hash dropped).
  • app-shell.spec.tsx: actions applied and dropped from the url, applied after /verify, not applied on hover.
  • Frontend: 70 files, 1456 tests passing; oxlint type check clean.
  • Not browser tested.

Generated by Claude Code

Miodec and others added 30 commits October 2, 2026 16:10
# Conflicts:
#	frontend/src/index.html
#	frontend/src/ts/components/common/Setting.tsx
#	frontend/src/ts/components/mount.tsx
#	frontend/src/ts/components/pages/test/TestPage.tsx
#	frontend/src/ts/components/pages/test/result/replay.ts
#	frontend/src/ts/controllers/chart-controller.ts
#	frontend/src/ts/elements/caret.ts
#	frontend/src/ts/elements/test-init-failed.ts
#	frontend/src/ts/event-handlers/test.ts
#	frontend/src/ts/index.ts
#	frontend/src/ts/test/caret.ts
#	frontend/src/ts/test/funbox/layoutfluid-funbox-timer.ts
#	frontend/src/ts/test/funbox/memory-funbox-timer.ts
#	frontend/src/ts/test/pace-caret.ts
#	frontend/src/ts/test/result.ts
#	frontend/src/ts/test/test-logic.ts
#	frontend/src/ts/test/test-screenshot.ts
#	frontend/src/ts/test/test-ui.ts
Adopt master's test page unmount (bindTestElements, optional carets) over
the branch's placeholder refs; test page show/hide logic stays in TestPage,
driven by the router signals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFv5oixtD54uhzpzvLu34N
- move route/loading/fade/transitioning state into router/page-transition.ts, driven by events from the router, withLoading and Pages
- route load failures go through tanstack (loaders throw) instead of a global error flag
- render the test page like every other page (drop the always-mounted shell)
- route replaceSearch through the router so its location stays in sync
- add isTestPageActive memo
- remove leftover lorem ipsum from settings page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LFv5oixtD54uhzpzvLu34N
- move navigation guards from navigate() into a router history blocker
  (covers links, navigate() and back/forward; redirects and
  replaceSearch bypass it)
- register router type so `to` is checked against the route tree
- Button takes `to`/`params` and renders Link; `href` is external only
- provide router context to all component roots so Link works outside
  RouterProvider
- remove initRouter click delegate, router-link attr and css hack
- remove unused `force` option from navigate()

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…into solid-pages

# Conflicts:
#	frontend/src/ts/components/layout/footer/ScrollToTop.tsx
…ith plain signals and functions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous page fades out before the next one mounts, tracked by a local
signal in Pages. Navigation is no longer blocked during fades - the fade
always swaps to the latest target. startTest checks the route instead.
isTestPageActive is set by TestPage while mounted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
router.state is backed by solid signals, so the route page and load error
are memos over its matches instead of copies set in router events. The
router instance is a signal in navigate.ts so state modules can read it
without importing the route tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resize re-centering, autofocus, refocus restarts and the monkey/modifier
key listeners are registered by bindTestElements and removed when the
test page unmounts, instead of global listeners checking the active page.
Code that touches test elements checks TestUI.isMounted().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Components use useMatchRoute, vanilla code uses isTestRoute (router.matchRoute
over the router signal). TestPage no longer sets global state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The route page only changes once the next route has loaded, so check the
target page instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
useIsTestRoute wraps useMatchRoute for components, listenForKeyState is
shared by the monkey and modifier listeners, and the loading page reset
reuses the initial indicator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…loaded signals

getSignal/signals read node values directly instead of the rAF-synced
mirror, signals registered after startup are tracked, and a hot reloaded
signal replaces the stale one with the same name and source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- drop isTestRoute/useIsTestRoute, use getRoutePage() everywhere
- loading-page.ts owns the loading page flag (hideLoadingPage)
- createSearchState + readSearchOnEnter for url search params
- test page lifecycle (ads, funbox, incomplete tests) moves into bindTestElements
- replaceSearch logs when the router isn't initialised

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Query reads inside the router suspend the nearest boundary (every route
is one), blanking the whole page. AsyncContent now wraps itself in
Suspense: its loader is the fallback, or its content without data when
alwaysShowContent is set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The router holder is typed as the app router via a type-only import, and
navigate takes typed { to, params, search } options instead of url
strings, so a broken internal link fails the type check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Miodec and others added 13 commits October 16, 2026 17:37
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Anime: apply the initial state at once, start the enter animation on the
  next frame (content mounting right after insertion no longer eats it)
- AnimePresence: an element that exits and comes back fades in again
- AnimeShow: animate the fallback like the content
- AnimeSuspense: Suspense that fades between fallback and content
- tests for the above, AnimePresence exitBeforeEnter ordering tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- PageSwitcher: keyed Show in AnimePresence instead of createSwitchTransition
  (page wrapper keeps the full-width grid layout)
- the router's pending component renders nothing: PageSwitcher shows the
  loading page; the loading page wrapper lives in PageSwitcher
- Page: a suspending page shows the loading page's spinner ("X page is
  still loading...")
- LoadingPage: no stale bar text under the spinner, spinner prop
- app-shell harness: animations finish at once, tests check the shown page.
  Fade ordering is tested with AnimePresence, plus a page's fade in starting
  after its content mounted

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	frontend/__tests__/components/common/anime/AnimePresence.spec.tsx
- comments still describing the createSwitchTransition page fade, the
  loading page as the router's pending component and the removed ADR
- PageSwitcher story: LoadingRoutePage is no longer exported, use an empty
  pending component like the app router

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… place

Content was read in Show/Suspense, so every change to it built a new
wrapper and animated it in again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Errors outside a loading step showed only their message. They now read
"Failed to load the {page} page: ...". Loading step failures throw a
LoadingStepError and keep their message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moves controllers/url-handler into router/url-actions. The test route's
beforeLoad applies shared links (testSettings, customTheme, challenge) and
the discord oauth hash once the user data is in, then redirects to drop them
from the url. Values are read from the raw search string so base64 and lz
"+" survive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8M7QTTf7Guriio4KfYJXv
@monkeytypegeorge monkeytypegeorge added the frontend User interface or web stuff label Oct 18, 2026
claude and others added 2 commits October 18, 2026 14:52
Each action (challenge, custom theme, test settings, discord oauth) owns
its link format: building the link and applying it. Typed as
UrlSearchAction or UrlHashAction, apply.ts applies them in order.

The discord oauth redirect_uri moves from /verify to /; old /verify
links are rewritten to / until they're gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@monkeytypegeorge monkeytypegeorge added the backend Server stuff label Oct 18, 2026
options: { history?: RouterHistory } = {},
) {
const history = options.history ?? createBrowserHistory();
// TODO: remove once links started before the discord oauth redirect moved

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A new Todo was discovered. If it is not a priority right now,consider marking it for later attention.
TODO: remove once links started before the discord oauth redirect moved

isDevEnvironment()
? `http%3A%2F%2Flocalhost%3A3000%2Fverify`
: `https%3A%2F%2Fmonkeytype.com%2Fverify`
? `http%3A%2F%2Flocalhost%3A3000%2F`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should use getFrontendUrl. Is /verifyremoved on purpose?
is the redirect uri validated by discord and need to be updated there as well?

Base automatically changed from router-rewrite to master October 19, 2026 22:04
Port pipeDelimiter=false fix (#8263) into url-actions test-settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 22:06
@github-actions github-actions Bot added the waiting for review Pull requests that require a review before continuing label Oct 19, 2026
@Miodec
Miodec merged commit fe8e40f into master Oct 19, 2026
15 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

It reworks the auth/OAuth and routing entry flow with documented behavior changes and a backend redirect-URI change that depends on external Discord config, and it is explicitly not browser-tested.

1 open issue
What changed in this PR

Folds the legacy controllers/url-handler.tsx (an authStateChanged side-effect that re-read window.location on every auth change) into the TanStack router. URL actions (shared test settings, custom theme, challenge, Discord OAuth token) are now applied once in the test route's beforeLoad, after user data loads, then stripped from the URL via a replace redirect so they aren't re-applied on reload or sign-in/out. Each action is a self-contained module behind a small framework (action.ts), and raw params are read from searchStr/hash to preserve + in base64/lz values. The Discord OAuth redirect now targets / instead of /verify, with /verify still handled for old links.

Changes:

  • Replaced the global auth-driven URL handler with per-action modules applied by the test route, driven by applyUrlActions(location).
  • Removed the verify route (now a pre-router /verify→/ rewrite) and pointed the backend Discord redirect URI at /.
  • Removed findGetParameter/checkIfGetParameterExists from misc.ts; migrated link builders and moved/added tests.
File Description
frontend/​src/​ts/​url-actions/​action.ts New action framework: encode/decode, readUrlAction, link builder.
frontend/​src/​ts/​url-actions/​apply.ts applyUrlActions entry point: detects, applies in order, drops params.
frontend/​src/​ts/​url-actions/​actions/​test-settings.ts Test-settings action + link builder, legacy format preserved.
frontend/​src/​ts/​url-actions/​actions/​custom-theme.ts Custom-theme action + link builder with revert-on-error.
frontend/​src/​ts/​url-actions/​actions/​challenge.ts Challenge action with case-insensitive name lookup.
frontend/​src/​ts/​url-actions/​actions/​discord-oauth.ts Discord token action reading the OAuth hash.
frontend/​src/​ts/​router/​routes.ts Test route beforeLoad applies actions + redirect; verify route removed.
frontend/​src/​ts/​router/​create-router.ts Pre-router /verify→/ history rewrite for old links.
frontend/​src/​ts/​index.tsx Dropped the url-handler side-effect import.
frontend/​src/​ts/​controllers/​url-handler.tsx Deleted (folded into url-actions).
frontend/​src/​ts/​utils/​misc.ts Removed findGetParameter/checkIfGetParameterExists.
frontend/​src/​ts/​components/​pages/​settings/​custom-setting/​Theme.tsx Uses buildCustomThemeLink.
frontend/​src/​ts/​components/​modals/​ShareTestSettings.tsx Uses buildTestSettingsLink.
frontend/​src/​ts/​collections/​utils/​blocking-download.ts Renamed getStateWhenReady→stateWhenReady to match collections.
frontend/​__tests__/​url-actions/​*.spec.ts New/moved unit tests for actions and applyUrlActions.
frontend/​__tests__/​router/​app-shell.spec.tsx Integration tests for applying/dropping actions and Discord flow.
frontend/​__tests__/​controllers/​url-handler.spec.ts Deleted (replaced by url-actions specs).
backend/​src/​utils/​discord.ts Discord OAuth redirect_uri changed /verify→/.

Review effort: Balanced

Comment on lines +49 to +50
? `http%3A%2F%2Flocalhost%3A3000%2F`
: `https%3A%2F%2Fmonkeytype.com%2F`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Server stuff frontend User interface or web stuff waiting for review Pull requests that require a review before continuing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants