oficina / meet Public

Pin estilo fix/menu-item-danger (759a8ff): End meeting for everyone in the negative tone

The row menu's destructive item uses estilo-menu-item="danger", the
correction this adoption made to estilo, in place of an app class. Native
hand-off pages show no account menu: they keep no-referrer, which a
chained sign-out cannot pass. The window scenario reads the new words.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev pushed by paul@keymail.dev 853b2b76895716465803ce9c994ac3ea9879edb0 parent fd53ffa
8 files changed, +23 −14
  • go.mod +1 −1
  • go.sum +2 −0
  • internal/calls/chrome.go +5 −3
  • internal/calls/native_auth.go +5 −2
  • internal/calls/static/calls-shell.css +0 −1
  • internal/calls/templates/index.html +1 −1
  • internal/callstest/chrome_test.go +1 −1
  • test/browser/call.mjs +8 −5

CI cancelled — superseded by a newer push — run details

diff --git a/go.mod b/go.mod
index 9d1c5e6..9f8c78d 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module amadan.net/oficina/calls
go 1.26.0
require (
- amadan.net/oficina/estilo.git v0.0.0-20260929231226-cce11010ece0
+ amadan.net/oficina/estilo.git v0.0.0-20261001081853-759a8ff71c5a
amadan.net/oficina/home/connect v0.0.0-20260930200947-7a0f19c3a8a7
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489
amadan.net/rastrillo/rastrillo v0.26.1-0.20260908075707-29e4491631bd
diff --git a/go.sum b/go.sum
index bfb810b..0f8bbb5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
amadan.net/oficina/estilo.git v0.0.0-20260929231226-cce11010ece0 h1:Qww+xGIYJaj+6TIX4Knr71ghxeYJjtgFm6WukX6NkGg=
amadan.net/oficina/estilo.git v0.0.0-20260929231226-cce11010ece0/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
+amadan.net/oficina/estilo.git v0.0.0-20261001081853-759a8ff71c5a h1:7eP/rgQjU0KAtYbhiaFSplK5VF2s49NeUKjki/li3zg=
+amadan.net/oficina/estilo.git v0.0.0-20261001081853-759a8ff71c5a/go.mod h1:4u0jNBtIdMLOsJyrScfqoEuYDJF7BBM3qM9YWbzt6aw=
amadan.net/oficina/home/connect v0.0.0-20260930200947-7a0f19c3a8a7 h1:FVzfiViGRUtl/enMtf34LL2ECLCLJx5V2JJmsOxuOEI=
amadan.net/oficina/home/connect v0.0.0-20260930200947-7a0f19c3a8a7/go.mod h1:YroavXe43Wietec+po8mtSk4a9Bir2h31Q3iAf4MWxI=
amadan.net/oficina/home/handoff v0.0.0-20260908102137-481d2d57f489 h1:RPKtyry/71i06o8L+gjAaUyexbmG56kO1YPs601URAM=
diff --git a/internal/calls/chrome.go b/internal/calls/chrome.go
index 7019ffc..5f1169f 100644
--- a/internal/calls/chrome.go
+++ b/internal/calls/chrome.go
@@ -45,9 +45,11 @@ type frame struct {
parts []string
// context is the open meeting's title, shown in the bar.
context string
- // signinPage is one of the sign-in, sent and signed-out pages: the
- // corner is empty and the brand is plain text, since everything it
- // could link to is behind the session gate.
+ // signinPage is one of the sign-in, sent and signed-out pages, or a
+ // native app hand-off page: the corner is empty and the brand is
+ // plain text, since everything it could link to is behind the session
+ // gate (and a hand-off page keeps no-referrer, which a chained
+ // sign-out could not pass through).
signinPage bool
// public is a page reached through a capability (the invitation
// link /j/{token}): "Sign in" carries no return path, because the
diff --git a/internal/calls/native_auth.go b/internal/calls/native_auth.go
index 1ce7d2f..be19dc7 100644
--- a/internal/calls/native_auth.go
+++ b/internal/calls/native_auth.go
@@ -242,7 +242,10 @@ func (a *app) nativeAuthorize(w http.ResponseWriter, r *http.Request) {
if person.Anonymous {
title = "Continue to the Calls app"
}
- render(w, "native", a.page(r, titled(title), map[string]any{"CancelHref": cancelHref, "Guest": person.Anonymous, "Title": title, "Action": "/native/authorize?id=" + id, "Done": false, "Code": protocol.AuthenticationCode(id)}))
+ // The hand-off pages keep no-referrer (nativeHeaders) and so show no
+ // account menu: an empty corner, as the sign-in pages have (spec §9.5,
+ // "not a chrome page").
+ render(w, "native", a.page(r, frame{parts: []string{title}, signinPage: true}, map[string]any{"CancelHref": cancelHref, "Guest": person.Anonymous, "Title": title, "Action": "/native/authorize?id=" + id, "Done": false, "Code": protocol.AuthenticationCode(id)}))
}
func (a *app) nativeApprove(w http.ResponseWriter, r *http.Request) {
nativeHeaders(w)
@@ -271,7 +274,7 @@ func (a *app) nativeComplete(w http.ResponseWriter, r *http.Request) {
if person.Anonymous {
title = "Calls is ready"
}
- render(w, "native", a.page(r, titled(title), map[string]any{"Title": title, "Done": true}))
+ render(w, "native", a.page(r, frame{parts: []string{title}, signinPage: true}, map[string]any{"Title": title, "Done": true}))
}
// nativeStartMeeting is POST /native/meetings/start: the app's half of
diff --git a/internal/calls/static/calls-shell.css b/internal/calls/static/calls-shell.css
index 91f71f0..517d6d6 100644
--- a/internal/calls/static/calls-shell.css
+++ b/internal/calls/static/calls-shell.css
@@ -30,4 +30,3 @@ body { background: var(--rst-bg); }
.calls-holding-more { display: grid; gap: var(--rst-sp-3); justify-items: start; margin-block-start: var(--rst-sp-5); }
.calls-holding-more p { margin: 0; }
.calls-holding-more form { margin: 0; }
-.calls-menu-item-danger { color: var(--rst-tone-negative-fg); }
diff --git a/internal/calls/templates/index.html b/internal/calls/templates/index.html
index e174840..ec405a8 100644
--- a/internal/calls/templates/index.html
+++ b/internal/calls/templates/index.html
@@ -25,7 +25,7 @@
<a estilo-menu-item href="/meetings/{{.Sqid}}/settings"><span estilo-menu-label>Meeting settings</span></a>
<a estilo-menu-item href="/meetings/{{.Sqid}}/links/replace"><span estilo-menu-label>Replace the link</span></a>
<hr>
- <a estilo-menu-item class="calls-menu-item-danger" href="/meetings/{{.Sqid}}/end"><span estilo-menu-label>End meeting for everyone</span></a>
+ <a estilo-menu-item="danger" href="/meetings/{{.Sqid}}/end"><span estilo-menu-label>End meeting for everyone</span></a>
</div>
</details>
</div>
diff --git a/internal/callstest/chrome_test.go b/internal/callstest/chrome_test.go
index 144976e..ee754b6 100644
--- a/internal/callstest/chrome_test.go
+++ b/internal/callstest/chrome_test.go
@@ -290,7 +290,7 @@ func TestMeetingsAreListRowsWithOneMenu(t *testing.T) {
`<span rst-row-main><a href="/m/` + sqid + `/join">Board meeting</a>`,
`<details rst-row-menu name="estilo-menus">`,
`<summary aria-label="Actions for Board meeting">`,
- `<a estilo-menu-item class="calls-menu-item-danger" href="/meetings/` + sqid + `/end"><span estilo-menu-label>End meeting for everyone</span></a>`,
+ `<a estilo-menu-item="danger" href="/meetings/` + sqid + `/end"><span estilo-menu-label>End meeting for everyone</span></a>`,
} {
if !strings.Contains(body, want) {
t.Errorf("the meeting list lacks %q", want)
diff --git a/test/browser/call.mjs b/test/browser/call.mjs
index ea14e9e..e376005 100644
--- a/test/browser/call.mjs
+++ b/test/browser/call.mjs
@@ -5080,7 +5080,7 @@ const run = {
'no script: the room is still the inset, rounded window, with no page scroll (' + JSON.stringify(gs) + ')');
const bareText = await still.locator('.calls-window').innerText();
check(bareText.includes('The call needs JavaScript') && bareText.includes('Back to the holding screen') &&
- bareText.includes('joining as a member'),
+ bareText.includes('You’re in this call as paul.'),
'no script: the title, the standing, the sentence and the way back are inside the window');
await still.locator('#call-nav summary').click();
const menu = await still.locator('.calls-nav-menu').innerText();
@@ -5130,15 +5130,18 @@ const run = {
'the title pill says the title and "In the meeting", as the Mac\'s does (' + JSON.stringify(pill) + ')');
await pageA.locator('#call-nav summary').click();
const standing = (await pageA.locator('.calls-nav-standing').innerText()).trim();
- check(standing === 'You are paul, joining as a member.' &&
+ // The standing line opens the menu; then the account menu's order
+ // (shared chrome R2): who is signed in, Meetings, Settings, Help.
+ check(standing === 'You’re in this call as paul.' &&
await pageA.evaluate(() => {
const p = document.querySelector('.calls-nav-standing');
- return !!p.nextElementSibling && p.nextElementSibling.textContent === 'Meetings';
- }), 'the standing line is in the title\'s menu, above Meetings (' + standing + ')');
+ const meetings = [...document.querySelectorAll('.calls-nav-menu a')].find((a) => a.textContent === 'Meetings');
+ return p === p.parentElement.firstElementChild && !!meetings && !!(p.compareDocumentPosition(meetings) & Node.DOCUMENT_POSITION_FOLLOWING);
+ }), 'the standing line opens the title\'s menu, above Meetings (' + standing + ')');
await pageA.keyboard.press('Escape');
check(!(await pageA.locator('#call-nav').evaluate((d) => d.open)), 'Escape closes the menu');
const floorText = await pageA.locator('#notices #floor-notice').innerText().catch(() => '');
- check(await pageA.locator('#floor-notice').isVisible() && floorText.includes('no relay yet'),
+ check(await pageA.locator('#floor-notice').isVisible() && floorText.includes('Calls outside your network may not connect yet.'),
'the relay notice is a notice of its own under the title, not in it');
await shot(pageA, '1440-relay-notice');
await pageA.locator('#floor-dismiss').click();