openvibers/openvibe.shared Commits

4.47.1: no page is wider than a phone (#190)

OP OpenVibers committed 14 days ago · 1fe48bbfbe · 10 files, +39 −11

A sideways row (.sc-row, .ovk-tabs, .ovk-stats) is position: relative, so a
hidden label (.sr-only, position: absolute) in a card past the edge stays in
the row: openvibe.coupons was 2208 px wide on a 390 px phone and
search.openvibe.network 3407 px. The phone hero's watermark sits inside the
hero's corner instead of 54 px past the screen (five homes 434-451 px wide).
docs/kit.md 'Scrolling on touch screens' writes down the rules.

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex ae7be03..4fcc297 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -6,6 +6,15 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
## Unreleased
++## 4.47.1 — 2026-10-30
++
++- **No page is wider than a phone.** A sideways row (`.sc-row`, `.ovk-tabs`, `.ovk-stats`) is `position: relative`, so a
++ hidden label (`.sr-only`, `position: absolute`) inside a card past the edge stays in the row instead of widening the
++ page. The phone hero's watermark (`.sc-hero-vis`) sits inside the hero's corner instead of 54 px past the screen;
++ five homes were 434-451 px wide on a 390 px phone.
++- docs/kit.md "Scrolling on touch screens": the rules for boxes, sheets and sideways rows on touch screens, and the
++ width check.
++
## 4.47.0 — 2026-10-29
- **The kit renders natively, on Android and iOS.** `scripts/native/compose/KitRenderer.kt` (`KitView(doc: KitNode)`, a
ddiff --git a/README.md b/README.md
iindex 5676277..0b7e784 100644
---- a/README.md
++++ b/README.md
@@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
--**Status:** alpha, v4.47.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.47.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
ddiff --git a/STATUS.json b/STATUS.json
iindex 7737c02..5ca55a5 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.47.0",
++ "version": "4.47.1",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
ddiff --git a/docs/kit.md b/docs/kit.md
iindex ae9401f..94dc505 100644
---- a/docs/kit.md
++++ b/docs/kit.md
@@@ -508,6 +508,24 @@ do most of that:
Lists that are text (questions, chips, table rows) are cut on phones instead, with `nth-child` and a link to the whole
list; a closing call that repeats the hero, or a "find your way around" grid that repeats the nav, is left out.
++### Scrolling on touch screens (4.47.1)
++
++A finger drag must always move something. The rules every site follows:
++
++- A short list flows in the page. It has no box of its own.
++- A long or live list (chat, a transcript, chapters on a phone) keeps a box no taller than 60% of the screen
++ (`max-height: min(60svh, 26rem)`) with `overscroll-behavior-y: auto`. At the box's end the drag carries on into
++ the page. `contain` there traps the finger (openvibe.video's chapters, 10-29).
++- Sheets, menus and dialogs keep `overscroll-behavior: contain` and lock the page behind them.
++- A sideways row (`.sc-row`, `.ovk-tabs`, a site's own card strip) is `position: relative` as well as `overflow-x: auto`.
++ Without that, a hidden label (`.sr-only`, `position: absolute`) inside a card past the edge is placed outside the
++ row and widens the whole page: openvibe.coupons was 2208 px wide on a 390 px phone and search.openvibe.network
++ 3407 px.
++- Decoration (a hero's watermark, a turning ring) stays inside its section, or the section sets `overflow-x: clip`.
++
++Check a page with `document.documentElement.scrollWidth`, which must equal the screen width at 320, 360 and 390 px,
++with and without touch.
++
## Balances
`wallet: true` (or a site's own `coins` and `vibes` chips) shows the balances as one calm pill on phones. Each balance
ddiff --git a/kit.css b/kit.css
iindex bf06bef..20af006 100644
---- a/kit.css
++++ b/kit.css
@@@ -83,7 +83,7 @@
.ovk-sub { margin: 2px 0 0; color: var(--ovk-muted); font-size: .86rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ovk-head-tools { display: flex; align-items: center; gap: 2px; margin-right: -8px; }
.ovk-head-tools form { margin: 0; }
--.ovk-tabs { display: flex; gap: 4px; margin: 0 -16px; padding: 0 16px 8px; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
++.ovk-tabs { position: relative; display: flex; gap: 4px; margin: 0 -16px; padding: 0 16px 8px; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.ovk-tabs::-webkit-scrollbar { display: none; }
.ovk-tabs a { display: inline-flex; align-items: center; gap: 6px; flex: none; min-height: 36px; padding: 6px 14px; border-radius: 999px; color: var(--ovk-muted); font-size: .9rem; font-weight: 650; text-decoration: none; white-space: nowrap; }
.ovk-tabs a:hover { color: var(--ovk-text); background: var(--ovk-panel-2); }
@@@ -209,7 +209,7 @@ div.ovk-set-row { cursor: default; }
}
/* ── Numbers, empty states, notices, code, sheets ── */
--.ovk-stats { display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
++.ovk-stats { position: relative; display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
.ovk-stats::-webkit-scrollbar { display: none; }
.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-panel); }
.ovk-stats dt { margin: 0; color: var(--ovk-muted); font-size: .76rem; line-height: 1.25; }
ddiff --git a/package.json b/package.json
iindex 7c0d3e4..f662baf 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.47.0",
++ "version": "4.47.1",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/room.css b/room.css
iindex a922e86..3893324 100644
---- a/room.css
++++ b/room.css
@@@ -29,7 +29,7 @@
--ovk-bottom: var(--ovnav-appbar-h, 0px); /* the app bar's height on phones (navbar.js sets it) */
--ovk-font: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);
}
--.ovk-stats { display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
++.ovk-stats { position: relative; display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
.ovk-stats::-webkit-scrollbar { display: none; }
.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-panel); }
.ovk-stats dt { margin: 0; color: var(--ovk-muted); font-size: .76rem; line-height: 1.25; }
ddiff --git a/showcase.css b/showcase.css
iindex 84ff081..c14688a 100644
---- a/showcase.css
++++ b/showcase.css
@@@ -27,7 +27,7 @@ a.sc-orb-node:hover .sc-orb-face>*:first-child,a.sc-orb-node:focus-visible .sc-o
html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow,html[data-ov-motion=reduced] .sc-orb-ring,html[data-ov-motion=reduced] .sc-orb-face{animation:none}
@media (max-width:860px){
.sc-hero.sc-has-vis{display:block}
--.sc-hero.sc-has-vis .sc-hero-vis{position:absolute;z-index:-1;top:-26px;right:-54px;width:180px;opacity:.36;pointer-events:none}
++.sc-hero.sc-has-vis .sc-hero-vis{position:absolute;z-index:-1;top:-26px;right:0;width:180px;opacity:.36;pointer-events:none}
.sc-hero.sc-has-vis .sc-aura .sc-vis-mark{width:46px!important;height:46px!important}
.sc-hero.sc-has-orbit{display:flex;flex-direction:column}
.sc-hero.sc-has-orbit .sc-hero-vis{order:-1;position:static;width:auto;opacity:1;pointer-events:auto;margin:0 0 6px}
@@@ -149,7 +149,7 @@ a.sc-stat:hover{border-color:var(--accent,#3b82f6)}
/* A list that is one sideways row on a phone (scroll snap, the next card peeking) and whatever the page made it on wide
screens. Every item stays in the HTML. --sc-row-w sets a card's width (82% by default; 46% for small tiles). */
@media (max-width:599px){
--.sc-row.sc-row{display:flex;flex-wrap:nowrap;overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding-inline:16px;scrollbar-width:none;gap:10px;margin-left:-16px;margin-right:-16px;padding:0 16px 4px;grid-template-columns:none}
++.sc-row.sc-row{position:relative;display:flex;flex-wrap:nowrap;overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding-inline:16px;scrollbar-width:none;gap:10px;margin-left:-16px;margin-right:-16px;padding:0 16px 4px;grid-template-columns:none}
.sc-row.sc-row::-webkit-scrollbar{display:none}
.sc-row.sc-row>*{flex:0 0 var(--sc-row-w,82%);min-width:0;scroll-snap-align:start}
.sc-fold-hint{display:none}
ddiff --git a/showcase.js b/showcase.js
iindex 596b268..9176123 100644
---- a/showcase.js
++++ b/showcase.js
@@@ -447,7 +447,7 @@ a.sc-orb-node:hover .sc-orb-face>*:first-child,a.sc-orb-node:focus-visible .sc-o
html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow,html[data-ov-motion=reduced] .sc-orb-ring,html[data-ov-motion=reduced] .sc-orb-face{animation:none}
@media (max-width:860px){
.sc-hero.sc-has-vis{display:block}
--.sc-hero.sc-has-vis .sc-hero-vis{position:absolute;z-index:-1;top:-26px;right:-54px;width:180px;opacity:.36;pointer-events:none}
++.sc-hero.sc-has-vis .sc-hero-vis{position:absolute;z-index:-1;top:-26px;right:0;width:180px;opacity:.36;pointer-events:none}
.sc-hero.sc-has-vis .sc-aura .sc-vis-mark{width:46px!important;height:46px!important}
.sc-hero.sc-has-orbit{display:flex;flex-direction:column}
.sc-hero.sc-has-orbit .sc-hero-vis{order:-1;position:static;width:auto;opacity:1;pointer-events:auto;margin:0 0 6px}
@@@ -569,7 +569,7 @@ a.sc-stat:hover{border-color:var(--accent,#3b82f6)}
/* A list that is one sideways row on a phone (scroll snap, the next card peeking) and whatever the page made it on wide
screens. Every item stays in the HTML. --sc-row-w sets a card's width (82% by default; 46% for small tiles). */
@media (max-width:599px){
--.sc-row.sc-row{display:flex;flex-wrap:nowrap;overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding-inline:16px;scrollbar-width:none;gap:10px;margin-left:-16px;margin-right:-16px;padding:0 16px 4px;grid-template-columns:none}
++.sc-row.sc-row{position:relative;display:flex;flex-wrap:nowrap;overflow-x:auto;scroll-snap-type:x mandatory;scroll-padding-inline:16px;scrollbar-width:none;gap:10px;margin-left:-16px;margin-right:-16px;padding:0 16px 4px;grid-template-columns:none}
.sc-row.sc-row::-webkit-scrollbar{display:none}
.sc-row.sc-row>*{flex:0 0 var(--sc-row-w,82%);min-width:0;scroll-snap-align:start}
.sc-fold-hint{display:none}
ddiff --git a/test/showcase.test.js b/test/showcase.test.js
iindex 97db5d7..d745026 100644
---- a/test/showcase.test.js
++++ b/test/showcase.test.js
@@@ -28,7 +28,8 @@ assert.ok(fo.includes('<div class="sc-fold-body"><section class="sc-sec"'), 'oth
assert.ok(sc.fold({ title: 'T', html: '<p>x</p>', open: true }).includes('<details class="sc-fold" open>'), 'open on request');
assert.strictEqual(sc.fold({ title: 'T', html: '' }), '', 'nothing to fold renders nothing');
assert.strictEqual(sc.fold({ html: '<p>x</p>' }), '', 'a fold needs a heading');
--assert.ok(sc.CSS.includes('.sc-row.sc-row{display:flex;flex-wrap:nowrap'), 'the phone row does not inherit a wrap');
++assert.ok(sc.CSS.includes('.sc-row.sc-row{position:relative;display:flex;flex-wrap:nowrap'), 'the phone row does not inherit a wrap, and holds what it clips (4.47.1)');
++assert.ok(/\.sc-hero\.sc-has-vis \.sc-hero-vis\{[^}]*right:0[;}]/.test(sc.CSS), 'the phone hero watermark stays inside the screen (4.47.1)');
// A set of six or nine apps is three a row; eight is four; seven keeps the grid's own columns.
const appsN = (n) => sc.apps({ items: Array.from({ length: n }, (_, i) => ({ name: `App ${i}`, href: `/a${i}` })) });