oficina / meet Public

Keep the in-call Share button and the scriptless one

a6c2ac8 fixed the scriptless regression by moving the Share form back out
of #room, which also undid the control-row placement c80a506 introduced
for parity with the native call bar. That traded one defect for a smaller
one. This keeps both.

The form now renders twice and never both at once. The control-row copy is
inside #room, which room.js un-hides, so it is the scripted path. The
<noscript> copy is the scriptless path: <noscript> is parsed as markup only
when scripting is off, so exactly one of the two is ever real, and the
sentence promising "everything else on this page works without it" is true
again — rather than made true by removing the feature it describes.

Worth recording why the earlier reading was wrong, since it nearly stood.
The 2026-09-12 spec note attributes "Share in the meeting view" to Paul,
which looked like it made the control-row placement his requirement. It
does not: ruling 16 was the invitation-token show-once rule, and what the
note supersedes is retrievability — that a member can get a link back later
rather than only at creation. At b6e8f29 the form was already on the room
page, above #room, so the request was satisfied before c80a506 moved it.
The placement inside the control row is design judgement, not the ask.

The regression test needed no change and now covers a third layout neither
version had. I checked it both ways again: it passes with the two copies,
and still fails when the <noscript> copy is deleted and only the control-row
one remains.

GOTOOLCHAIN=go1.26.5 make ci passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 26d75a9eb6fb2ffa47325b0a21c2282c6843594b parent a6c2ac8
2 files changed, +25 −10
  • internal/calls/templates/room.html +9 −2
  • internal/callstest/room_test.go +16 −8

CI failed — run details

diff --git a/internal/calls/templates/room.html b/internal/calls/templates/room.html
index edfda3d..56cd2fa 100644
--- a/internal/calls/templates/room.html
+++ b/internal/calls/templates/room.html
@@ -18,8 +18,14 @@
<p>You are {{.Name}}, joining as a {{.Standing}}.</p>
{{if .Floor}}<p rst-notice>This team's Calls has no relay yet. Calls usually connect only between people on the same network.</p>{{end}}
-{{if .IsMember}}<form method="post" action="/m/{{.Sqid}}/share" target="_blank" rel="noopener" data-busy="false"><button type="submit" rst-btn aria-label="Share meeting link">Share link</button></form>{{end}}
-<noscript><p>The call needs JavaScript to reach your camera and the other participants; everything else on this page works without it. <a href="/m/{{.Sqid}}/join">Back to the holding screen</a>.</p></noscript>
+{{/* The Share form appears twice on purpose, and never at the same
+ time. The control-row copy below is inside #room, which room.js
+ un-hides, so it is the scripted path and matches the native call
+ bar. This copy is the scriptless one: <noscript> is parsed as
+ markup only when scripting is off, so exactly one of the two is
+ ever real. Without it the sentence above is false, which is how
+ it stood between c80a506 and a6c2ac8. */}}
+<noscript><p>The call needs JavaScript to reach your camera and the other participants; everything else on this page works without it. <a href="/m/{{.Sqid}}/join">Back to the holding screen</a>.</p>{{if .IsMember}}<form method="post" action="/m/{{.Sqid}}/share" target="_blank" rel="noopener" data-busy="false"><button type="submit" rst-btn aria-label="Share meeting link">Share link</button></form>{{end}}</noscript>
<div id="room" data-sqid="{{.Sqid}}" data-boot="{{.Boot}}" data-self-name="{{.Name}}" data-is-member="{{if .IsMember}}1{{end}}" data-relay="{{if .Relay}}1{{end}}" data-peer="" hidden>
<div id="call-workspace" estilo-call-workspace="sidebar">
<div class="calls-stage">
@@ -30,6 +36,7 @@
<button type="button" id="camera" rst-btn aria-label="Camera" aria-pressed="false">{{icon "video"}}Camera<span id="camera-state" class="calls-toggle-state" aria-hidden="true">On</span></button>
<button type="button" id="share" rst-btn aria-pressed="false">{{icon "monitor"}}Share screen<span id="share-state" class="calls-toggle-state" aria-hidden="true">Off</span></button>
<button type="button" id="devices-toggle" rst-btn aria-expanded="false" aria-controls="room-devices" hidden>{{icon "sliders-horizontal"}}Devices</button>
+ {{if .IsMember}}<form method="post" action="/m/{{.Sqid}}/share" target="_blank" rel="noopener" data-busy="false"><button type="submit" rst-btn aria-label="Share meeting link">Share link</button></form>{{end}}
<button type="button" id="toggle-chat" rst-btn aria-expanded="true" aria-controls="chat" hidden>{{icon "message-circle"}}Chat<span id="chat-unread" aria-hidden="true"></span></button>
<button type="button" id="leave" rst-btn="danger">{{icon "log-out"}}Leave</button>
</div>
diff --git a/internal/callstest/room_test.go b/internal/callstest/room_test.go
index c98f23e..570ebe6 100644
--- a/internal/callstest/room_test.go
+++ b/internal/callstest/room_test.go
@@ -42,14 +42,22 @@ func TestRoomPageRendersItsHeaderStandingAndScriptlessFallback(t *testing.T) {
if headEnd < 0 || css < 0 || css > headEnd {
t.Fatalf("calls.css is not inside <head>: head ends at %d, calls.css at %d\n%s", headEnd, css, body)
}
- // The member Share-link form must sit outside #room, which is hidden
- // until room.js runs. It lived inside #controls between c80a506 and
- // 03588e2, which put a member action behind JavaScript while the
- // <noscript> paragraph directly above went on promising "everything
- // else on this page works without it". AGENTS.md makes that floor
- // load-bearing with two named exceptions, and sharing is not one of
- // them. Position, not presence, is what regressed: the form rendered
- // either way, so any Contains check would have stayed green.
+ // A member must be able to reach Share without JavaScript. The form
+ // is rendered twice and never both at once: the control-row copy is
+ // inside #room, which room.js un-hides, and the <noscript> copy is
+ // the scriptless path. This asserts the first occurrence precedes
+ // #room, so deleting the <noscript> copy and leaving only the
+ // control-row one fails.
+ //
+ // Between c80a506 and a6c2ac8 only the control-row copy existed, so
+ // a member action sat behind JavaScript while the <noscript>
+ // paragraph went on promising "everything else on this page works
+ // without it". AGENTS.md makes that floor load-bearing with two
+ // named exceptions, and sharing is not one of them.
+ //
+ // Position, not presence, is what regressed: the form rendered in
+ // both layouts, so every Contains-style assertion stayed green.
+ // That is why this compares offsets.
share := strings.Index(body, `action="/m/`+sqid+`/share"`)
room := strings.Index(body, `<div id="room"`)
if share < 0 || room < 0 || share > room {