docs: tighten the right rail tabs comments, docs and changelog

Drop change-history wording, shorten the module, stylesheet and rail comments, and document the owner rule's stored-over-default step in CURRENT-STATE. No runtime change.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Fable 5.1 committed 2026-10-27 18:37:15 +04:00
1 parent f383174f3e
commit cf37e80b7f
7 files changed
+101 -118

No files matched your search

+6 -8
View File
@@ -1,10 +1,8 @@
- The right rail (Display, CCTV, Street Level, Context, plus Weather and Recent
Imagery while shown) is a vertical tab strip with sideways labels instead of
an accordion of collapsed launcher cards: the open panel sits beside the
strip and at most one panel is open in every desktop HUD layout. The open
tab and its panel share one surface and one outline, like a folder tab; the
other tabs are plain labels. A first-time visitor on a desktop finds Display
open, so the strip reads as launchers; a stored choice still wins. The tabs
are a keyboard-navigable tablist, Escape inside a panel returns focus to its
tab, and saved preferences and share links are unchanged. Phones keep their
sheet shell (gekh, #1425).
an accordion of collapsed launcher cards: at most one panel is open in every
desktop HUD layout, and the open tab and its panel share one surface and
outline, like a folder tab. A first-time visitor on a desktop finds Display
open; a stored choice still wins. The strip is a keyboard-navigable tablist
and Escape inside a panel returns focus to its tab. Saved preferences, share
links and the phone sheet shell are unchanged (gekh, #1425).
+46 -48
View File
@@ -226,8 +226,8 @@ Chamfered panel expand/collapse controls use an inset keyboard-focus outline and
red hover border in both map and cockpit, so clipping does not hide the highlight.
Cyber skins the map-side right rail's tab strip in its red and slate panel
treatment; the one-panel rule it introduced now belongs to every desktop layout
(see "Right rail tabs" below). Display, CCTV and Context scroll their contents
treatment; the one-panel rule applies in every desktop layout (see "Right rail
tabs" below). Display, CCTV and Context scroll their contents
inside fixed headers and decorative frames. The narrow-screen rail remains
scrollable to reach each panel. Radio retains the shared nested Context player
and compact header disclosure, without relocating playback controls on theme
@@ -1476,11 +1476,12 @@ The left rail keeps its measured collapsed heights and obstacle-safe corridor;
the right rail follows its top baseline and preserves Display's scroll owner.
The left rail's automatic collapse remains presentation only and prioritizes
the latest explicit panel. The right rail keeps one expanded owner (the latest
explicit panel, else the panel holding keyboard focus, else the first) and
presentation-collapses its peers with `rail-owner-collapsed`, which share links
and the phone shell read as "expanded"; it needs no second pass. Mobile layout
still releases desktop allocation styles and gives every owner-collapsed panel
back. Stable Display allocation avoids unnecessary style writes.
explicit panel, else the focused one, else one with a stored or shared choice
over one open only by default, else the first) and presentation-collapses its
peers with `rail-owner-collapsed`, which share links and the phone shell read
as "expanded". Mobile layout still releases desktop allocation styles and gives
every owner-collapsed panel back. Stable Display allocation avoids unnecessary
style writes.
Context expansion through the panel owner or adaptive right-rail restoration
closes an open compact Radio disclosure before Context/Space Missions becomes
visible. The mini remains available while Context is collapsed. This fixes the
@@ -1492,46 +1493,42 @@ rule can still give them back.
### Right rail tabs
On desktop the right rail (Display, CCTV, Street Level, Context, plus Weather
and Recent Imagery while they are shown) is a vertical tab strip
and Recent Imagery while shown) is a vertical tab strip
(`src/ui/rightRailTabs.js`, `src/ui/styles/right-rail-tabs.css`): one sideways
tab per panel hugs the rail's right edge, the open panel sits beside it, and
collapsed panels are hidden in every HUD layout, so the strip is the only
launcher. A tab toggles its panel through `setPanelCollapsed` with the explicit
flag, so opening one collapses the others and the saved preference, share link
and Context/Radio ownership rules are unchanged; the panels' `collapsed`
classes remain the only state and the strip mirrors them through a
MutationObserver. The strip is a `role="tablist"` with vertical orientation:
Up/Down wrap, Home/End jump, the open tab (else the first) is the tab stop, and
Escape inside a panel returns focus to that panel's tab. The strip precedes the
panels in the DOM so Tab from the open tab enters the open panel. Labels are
the panels' own translated header titles. The strip marks the rail with
`data-rail-tabs`, which the stylesheet uses to reserve the strip's column; a
rail without the strip keeps its legacy geometry. Phones hide the strip and
keep the sheet shell; Cockpit hides the rail and the strip with it.
tab per panel at the rail's right edge, the open panel beside it, collapsed
panels hidden in every HUD layout. A tab toggles its panel through
`setPanelCollapsed` with the explicit flag, so opening one collapses the others
and saved preferences, share links and the Context/Radio ownership rules are
unchanged; the panels' `collapsed` classes remain the only state, mirrored
through a MutationObserver. The strip is a vertical `role="tablist"`: Up/Down
wrap, Home/End jump, the open tab (else the first) is the tab stop, Escape
inside a panel returns focus to its tab, and the strip precedes the panels in
the DOM so Tab from the open tab enters the panel. Labels are the panels' own
translated header titles. The strip marks the rail with `data-rail-tabs`, which
the stylesheet keys the strip's column on; a rail without the strip keeps its
plain geometry. Phones hide the strip and keep the sheet shell; Cockpit hides
the rail and the strip with it.
The open tab and its panel are one surface, like a folder tab, and the other
tabs are borderless labels. The strip measures the open panel and tab,
traces their joined silhouette as an SVG path (`railJoinPath`: rounded panel
corners, the tab's outer corners, concave fillets where the tab meets the
panel's edge; Cyber's chamfered frame with its calibration bay and a square
tab instead) and keeps a join element under the panels: one layer blurs and
tints the backdrop inside the path, an SVG strokes the outline along it and
another SVG casts the drop shadow, so the junction has no seam, doubled edge
or second blur region. The open panel's wrapper and card and the tab draw no
surface, border, shadow or blur of their own (Cyber dresses the wrapper too). The join is re-measured through a
ResizeObserver and on strip scroll; `--rail-tab-bottom` on the rail sets the
open panel's minimum height, so a short panel such as Context still reaches
its tab. A first tab level with the panel's top shares its top edge, with no
corner between them; Cyber's strip starts below the frame's upper chamfer.
The open tab and its panel are one surface, like a folder tab; the other tabs
are borderless labels. The strip measures the open panel and tab, traces their
joined silhouette as an SVG path (`railJoinPath`: rounded corners, concave
fillets at the junction; Cyber's chamfered frame with its calibration bay and a
square tab) and keeps a join element under the panels: one layer blurs and
tints the backdrop inside the path, one SVG strokes the outline and another
casts the drop shadow, so the junction has no seam or second blur region. The
open panel's wrapper and card and the tab draw no surface of their own. The
join is re-measured through a ResizeObserver and on strip scroll;
`--rail-tab-bottom` on the rail sets the open panel's minimum height, so a
short panel such as Context still reaches its tab. A first tab level with the
panel's top shares its top edge; Cyber's strip starts below the frame's upper
chamfer.
A first-time visitor on a desktop viewport finds Display open
(`src/ui/panelPositionControls.js`): with every panel closed nothing shows
that the sideways labels are launchers, so the first panel opens to
demonstrate the strip. Phones keep it closed (their sheets present the
rail), a share link decides for itself, and a stored choice wins either way.
When the rail picks its one owner, a panel the visitor or a share link opened
outranks one open only by default, so a stored CCTV choice is not displaced
by the Display default.
(`src/ui/panelPositionControls.js`): with every panel closed nothing shows that
the labels are launchers. Phones keep it closed, a share link decides for
itself, and a stored choice wins either way. When the rail picks its owner, a
panel the visitor or a share link opened outranks one open only by default, so
a stored CCTV choice is not displaced by the Display default.
## Phone layout
@@ -1568,8 +1565,9 @@ sheet before a panel's own Escape handling, and the dock's hover auto-close does
not apply inside a sheet.
Cockpit, recording and clean view close the sheet and hide the tab bar; scene
playback keeps both so its STOP control in the Scenes panel stays reachable. The
right rail neither hides collapsed siblings, shows its tab strip, nor applies
the desktop one-panel rule at these widths, since the sheet chooses what shows. The Cesium and Google attribution stays visible above the
right rail hides no collapsed siblings, shows no tab strip and applies no
one-panel rule at these widths, since the sheet chooses what shows. The Cesium
and Google attribution stays visible above the
voice control, and above an open sheet. The page draws under the notch and
system bars (`viewport-fit=cover`) and the shell's chrome uses safe-area insets.
Wider viewports are unchanged, and crossing to them closes any open sheet.
@@ -2166,9 +2164,9 @@ fetching, trailing-24-hour filtering and partial-success caching are unchanged.
> Missions · Environmental · Explore manually**. No layer and no optional API
> call happens until a tile is clicked. The right-hand DISPLAY rail
> (`pp-toggles`) now starts **collapsed** on a first run rather than expanded —
> a stored collapse state still wins, as before. (Superseded 2026-10-27 by the
> right rail tabs: on a desktop viewport DISPLAY starts open again on a first
> run; see "Right rail tabs".)
> a stored collapse state still wins, as before. (Superseded 2026-10-27: with
> the right rail tabs DISPLAY starts open on a first desktop run; see "Right
> rail tabs".)
>
> **The ENVIRONMENTAL tile is quakes AND fires** — live USGS earthquakes plus
> NASA FIRMS active fires (`layerIds: ['earthquakes', 'local-firms']`), with the
+5 -7
View File
@@ -70,13 +70,11 @@ export class PanelPositionControls {
// storage unavailable
}
}
// DISPLAY starts EXPANDED for a first-time visitor on a desktop viewport,
// where the right rail is a strip of label tabs (rightRailTabs.js): with
// every panel closed nothing shows that the labels are launchers, so the
// first panel opens to demonstrate the strip. Phones keep it collapsed
// (their sheets present the rail), and a share link decides for itself.
// A stored choice still wins in both directions, so anyone who closes it
// keeps it closed.
// DISPLAY starts EXPANDED for a first-time visitor on a desktop viewport:
// the right rail is a strip of label tabs (rightRailTabs.js), and with
// every panel closed nothing shows that the labels are launchers. Phones
// keep it collapsed (their sheets present the rail); a share link decides
// for itself. A stored choice wins in both directions.
if (panelId === 'pp-toggles' && stored === null)
collapsed = !allowStored || isPhoneViewport();
panelEl.classList.toggle('collapsed', collapsed);
+5 -8
View File
@@ -62,14 +62,11 @@ export function resolveHudRailLayout({
}
/**
* The right rail's tab strip (rightRailTabs.js) is the launcher in every HUD
* layout, so a collapsed panel never stays visible as a card. The rail state
* argument is accepted for callers that describe what they lay out.
*
* @param {object} [input] Current rail state.
* @param {string} [input.hudVariant] Active HUD layout variant.
* @param {boolean} [input.hasExpandedPanel] Whether any rail panel is expanded.
* @returns {boolean} Whether collapsed sibling panels should be hidden.
* The tab strip (rightRailTabs.js) is the launcher in every HUD layout, so
* collapsed right-rail panels always hide. Callers still pass the rail state
* they lay out.
* @param {{hudVariant?: string, hasExpandedPanel?: boolean}} [input]
* @returns {boolean}
*/
export function shouldHideCollapsedRightPanels(input = {}) {
void input;
+6 -7
View File
@@ -74,13 +74,12 @@ export function layoutRightPanelRail({
!panel.hidden &&
(hiddenByRail(panel) || getComputedStyle(panel).display !== 'none'),
);
// One expanded owner: the latest explicit disclosure, else the panel that
// holds keyboard focus, else one the visitor or a share link chose over one
// open only by default (panelPositionControls.js marks each), else the
// first. Peers are presentation-collapsed without overwriting saved panel
// preferences; the marker is distinct from the left rail's space-based
// collapse. Phones present the rail as tab sheets that show several panels
// at once, so they get every panel back.
// One expanded owner: the latest explicit disclosure, else the focused
// panel, else one the visitor or a share link chose over one open only by
// default (panelPositionControls.js marks each), else the first. Peers are
// presentation-collapsed; the marker is distinct from the left rail's
// space-based collapse. Phone sheets show several panels at once, so phones
// get every panel back.
const isMobile = windowRef.matchMedia('(max-width: 720px)').matches;
if (!isMobile) {
const expanded = panels.filter(
+18 -21
View File
@@ -1,17 +1,15 @@
/**
* Vertical tab strip for the right context rail.
* Vertical tab strip for the right context rail: one sideways tab per rail
* panel, the open panel beside the strip, at most one panel open. The panels'
* `collapsed` classes remain the only state; the strip mirrors them and asks
* the owner (setPanelCollapsed) for every change, so persistence, share links
* and the rail layout pass are untouched. Phones hide the strip and keep their
* sheet shell (mobileShell.js).
*
* The strip replaces the rail's collapsed launcher cards: one sideways tab per
* rail panel, the open panel beside the strip, at most one panel open. The
* panels' `collapsed` classes remain the only state. The strip mirrors them and
* asks the owner (setPanelCollapsed) for every change, so persistence, share
* links and the rail layout pass are untouched. Phones hide the strip and keep
* their sheet shell (mobileShell.js).
*
* The open tab and its panel are drawn as one surface: the strip measures
* both, traces their joined silhouette as an SVG path (railJoinPath) and keeps
* a join element under the panels whose layers blur, tint, outline and shadow
* that one shape (styles/right-rail-tabs.css).
* The open tab and its panel are one surface: the strip measures both, traces
* their joined silhouette as an SVG path (railJoinPath) and keeps a join
* element under the panels whose layers blur, tint, outline and shadow that
* shape (styles/right-rail-tabs.css).
*/
import { msg, t } from '../i18n/index.js';
@@ -19,8 +17,7 @@ export const RIGHT_RAIL_TABS_ID = 'right-rail-tabs';
/**
* Marks the rail while its strip is mounted; styles/right-rail-tabs.css keys
* the rail's tab geometry on it so a rail without a strip keeps its legacy
* layout.
* the tab geometry on it, so a rail without a strip keeps its plain layout.
*/
export const RIGHT_RAIL_TABS_ATTRIBUTE = 'data-rail-tabs';
@@ -242,8 +239,8 @@ export function createRightRailTabs({
const observer =
typeof Observer === 'function' ? new Observer(() => sync()) : null;
// Tab heights follow their labels (fonts, locale) and panel heights their
// content and the rail's allocation, so the join is re-measured whenever
// the strip, a tab or a panel changes size or the strip scrolls.
// content and allocation: re-measure the join on any size change and on
// strip scroll.
const Resize = documentRef.defaultView?.ResizeObserver;
const resizer =
typeof Resize === 'function' ? new Resize(() => sync()) : null;
@@ -265,11 +262,11 @@ export function createRightRailTabs({
};
/**
* Publish the join: the open tab's extent on the rail (--rail-tab-top,
* --rail-tab-bottom, rail pixels, for the panel's minimum height) and the
* joined silhouette on the join element's layers. The rail itself can be
* taller than its panel in the focus layout, so the join is sized from
* the panel, extended down to cover the tab if the panel is shorter.
* Publish the join: the open tab's extent on the rail (--rail-tab-top and
* --rail-tab-bottom, rail pixels; the stylesheet sets the panel's minimum
* height from them) and the joined silhouette on the join's layers. The
* rail can be taller than its panel in the focus layout, so the join is
* sized from the panel, extended down to cover the tab if that is shorter.
*/
function placeJoin(tab, panel) {
const style = rail.style;
+15 -19
View File
@@ -4,19 +4,15 @@
exclusive layout (rightPanelRail.js) hides the collapsed ones, so the strip
is the only launcher. Phones keep the sheet shell (mobile-shell.css).
The open tab and its panel are one surface. The strip builds a join
element (.right-rail-join) under the panels from the measured geometry:
one path traces the silhouette of the panel with the tab attached to its
right edge (rounded corners, concave fillets at the junction; Cyber's
chamfered frame instead). The glass layer blurs and tints the backdrop
inside that path, an SVG strokes the outline along it and another SVG
casts the drop shadow, so there is no seam, no doubled edge and no second
blur region where the tab meets the panel. The open panel's card and the
tab draw no surface of their own; the other tabs are borderless labels.
The strip also publishes --rail-tab-bottom on the rail so a short panel
grows down to its tab. `data-rail-tabs` arrives on the rail with the
strip, so a rail without it keeps its legacy geometry; the selectors carry
it so they outrank the Cyber rail rules that follow this sheet. */
The open tab and its panel are one surface. The strip builds a join element
(.right-rail-join) under the panels: one path traces the panel with the tab
attached (rounded corners and concave fillets; Cyber's chamfered frame).
The glass layer blurs and tints the backdrop inside the path, one SVG
strokes the outline and another casts the shadow, so the junction has no
seam and no second blur region. The open panel and the tab draw no surface
of their own; the other tabs are plain labels. `data-rail-tabs` arrives on
the rail with the strip, so a rail without it keeps its plain geometry; the
selectors carry it to outrank the Cyber rail rules that follow this sheet. */
#right-context-rail[data-rail-tabs] {
--right-rail-panel-width: 330px;
@@ -160,8 +156,8 @@ body.mobile-shell #right-context-rail > .right-rail-join {
}
/* The open panel's card leaves surface, outline and shadow to the join.
Cyber draws Display's and Context's frames, and the surface contract its
card, on a ::before. */
Cyber's Display and Context frames and the surface contract's card live on
a ::before. */
body:not(.mobile-shell)
#right-context-rail[data-rail-tabs]
> #pp-toggles:not(.collapsed),
@@ -190,7 +186,8 @@ body:not(.mobile-shell)
-webkit-backdrop-filter: none;
}
/* The card's glow used to sit under its own surface; keep it as faint. */
/* The glow's 0.25 is tuned to sit under the card's surface; without that
surface, 0.08 reads the same. */
body:not(.mobile-shell)
#right-context-rail[data-rail-tabs]
> [data-panel-id]:not(.collapsed)
@@ -256,9 +253,8 @@ body:not(.mobile-shell)
color: var(--cyber-red-bright);
}
/* Cyber also dresses every .panel-collapsible wrapper as a surface with an
inset ring (cyber.css); under the tabs the join is the only surface, or
the ring shows as a line along the panel's edge beside the open tab. */
/* Cyber gives every .panel-collapsible wrapper a surface with an inset ring
(cyber.css); beside the open tab that ring would show as a line. */
:root[data-ui-theme='cyber']
body:not(.mobile-shell)
#right-context-rail[data-rail-tabs]