Skip to content

feat(tabs): the address names the open tab, and same-page links keep it - #736

Merged
braghettos merged 1 commit into
mainfrom
feat/tabs-keep-address
Nov 15, 2026
Merged

braghettos merged 1 commit into
mainfrom
feat/tabs-keep-address

Conversation

@braghettos

Copy link
Copy Markdown
Collaborator

The bug

Found on krateo-057 while checking portal 1.8.126, read-only. On /observability, while on the Telemetry tab, picking a time range (1h / 6h / 24h / 7d) lands you back on the first tab, Reconciliation.

  • The range chips navigate to /observability?range=1h&… without ?tab.
  • The Tabs widget reads ?tab=<label> once on load (defaultActiveKey) and never writes it. Clicking a tab doesn't change the address, so no link can carry the open tab.

Diego's decision (2026-11-15): tabs keep the address in step.

The change

  • widgets/Tabs. onChange replaces ?tab=<label> in the current history entry (history.replaceState(window.history.state, …), the same way context/pageFragment does). Back still leaves the page, a copied link reopens the tab, and nothing is written if the address already names it.
  • utils/navigation. PAGE_STATE_PARAMS = ['tab'] rides along in carryScopeParams (which navigateOrExternal applies to every widget navigation) only when the target stays on the same page. It never goes to another page, whose tabs differ, and a target that names its own tab wins. ?projects keeps working exactly as before.
  • hooks/useWidgetQuery. buildExtrasParam leaves tab out of the extras. tab is page state, not a data input (no portal template reads it), so every widget's react-query key and snowplow L1 cell stay the same whichever tab the address names. Switching tabs refetches nothing, and neither does opening a deep link.

Before this, tab (when present on a deep link) went into every widget's key. Dropping it means those URLs map to the same cells as the plain page.

Why not just add ?tab to the portal's range chips? Their routes are computed server-side when they load. Clicking a tab doesn't refetch them, so they'd carry a stale tab. Carrying page state at navigation time is the one place that always sees the open tab, and it needs no portal change.

Evidence

  • navigation.test.ts.
    • The tab is kept on a same-page target (/observability?range=1h from ?tab=Telemetry → …&tab=Telemetry).
    • It's not carried to another page (/compositions?kind=Installer).
    • A target's own tab wins.
    • Scope and tab travel together.
  • Tabs.deeplink.test.tsx. Clicking Telemetry calls replaceState once with /observability?range=24h&tab=Telemetry and never pushState. If the address already names the tab, nothing is written.
  • tabPageState.extras.test.ts. tab is dropped and every other key keeps its order; the key is identical with or without ?tab; a URL holding only ?tab gives the empty envelope.
  • vitest over navigation, Tabs, the new test and the existing extras tests (callerExtras, useWidgetQuery, widgetScope.extras, pageFragment): 7 files, 103 tests passed. npm run typecheck and eslint on the changed files: clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01N8HNbChC1PxtHfCivwZLyD

On /observability, picking a time range jumped the reader from the tab
they were on (Telemetry) back to the first tab: the range chips navigate
to /observability?range=1h, and the Tabs widget read ?tab only on load
and never wrote it, so nothing could carry it (Diego, 2026-11-15: tabs
keep the address in step).

- Tabs: choosing a tab replaces ?tab=<label> in the current history
  entry (history.replaceState with the current state, as pageFragment
  does): no new entry, no router navigation, no re-render.
- navigation: `tab` is PAGE STATE, carried only on a navigation that
  stays on the same page (never to another page, whose tabs differ),
  and a target that names its own tab wins.
- useWidgetQuery: buildExtrasParam leaves `tab` out, so every widget's
  key and snowplow L1 cell are the same whichever tab the address names.

No schema or CRD change; no portal change needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8HNbChC1PxtHfCivwZLyD
@braghettos
braghettos merged commit 10d7af5 into main Nov 15, 2026
29 checks passed
@braghettos
braghettos deleted the feat/tabs-keep-address branch November 15, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant