oficina / meet Public

Put the Share form back outside #room, and pin it there

Found by oficina-14's adversarial review of 772c1e0. Both defects predate
the rename; they arrived with the web/native-consistency branch.

The member Share-link form moved into #controls, which is inside
<div id="room" hidden>. #room stays hidden until room.js runs, so with
JavaScript off the form did not render at all โ€” while the <noscript>
paragraph immediately above went on saying "everything else on this page
works without it". AGENTS.md makes the scriptless floor load-bearing with
two named exceptions, and sharing a meeting link is not one of them. I
verified it against history rather than taking it on report: at b6e8f29
the form sat at line 13, ahead of #room at line 15.

What regressed was position, not presence. The form rendered either way,
so every Contains-style assertion stayed green and nothing caught it. The
new check compares the two offsets, and I confirmed it fails against the
old layout before keeping it โ€” without that it would only have been a test
that agreed with whatever the template happened to do.

Also restores the comment deleted in c80a506, which was the only record
that chat.js must load before room.js: both are deferred, deferred scripts
run in document order, and that ordering is what puts window.Chat in place
before room.js mounts it. Reorder the two script tags without it and chat
silently never un-hides.

GOTOOLCHAIN=go1.26.5 make ci passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev a6c2ac806e199d1464941f52686e76c2548d652f parent 03588e2
2 files changed, +19 −1
  • internal/calls/templates/room.html +5 −1
  • internal/callstest/room_test.go +14 −0

CI failed — run details

diff --git a/internal/calls/templates/room.html b/internal/calls/templates/room.html
index 8956f83..edfda3d 100644
--- a/internal/calls/templates/room.html
+++ b/internal/calls/templates/room.html
@@ -18,6 +18,7 @@
<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>
<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">
@@ -29,7 +30,6 @@
<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>
@@ -59,6 +59,10 @@
to live somewhere that is not about to be overwritten. */}}
<p id="device-note" class="calls-device-note" role="status" hidden></p>
</div>
+ {{/* The chat panel (spec ยง6): one partial, one script, one
+ comment in partials/chat.html. chat.js loads before room.js in
+ the head block, and two deferred scripts run in document order,
+ so window.Chat is there when room.js mounts it. */}}
{{template "chat-panel" .}}
</div>
<div id="queue" class="calls-queue" hidden><h2>Waiting to join</h2><ul id="knocks"></ul></div>
diff --git a/internal/callstest/room_test.go b/internal/callstest/room_test.go
index 707b1d2..c98f23e 100644
--- a/internal/callstest/room_test.go
+++ b/internal/callstest/room_test.go
@@ -42,6 +42,20 @@ 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.
+ share := strings.Index(body, `action="/m/`+sqid+`/share"`)
+ room := strings.Index(body, `<div id="room"`)
+ if share < 0 || room < 0 || share > room {
+ t.Fatalf("share form is not outside #room: share at %d, #room at %d\n%s", share, room, body)
+ }
+
// The no-standing case belongs to
// TestRoomAndTakeoverAreGatedByStandingOrParticipation: this
// harness's member allowlist refuses to even mail a magic link to