From b50a80674e24cf2380f63035b243ff1118269080 Mon Sep 17 00:00:00 2001 From: Brittany Ellich Date: Mon, 1 Jun 2026 07:39:48 -0700 Subject: [PATCH] feat(qr): add scan-to-connect button to home dashboard Co-Authored-By: Claude Opus 4.8 --- features/index/pages/index.templ | 6 +- features/index/pages/index_templ.go | 120 +++++++++++++++------------- 2 files changed, 66 insertions(+), 60 deletions(-) diff --git a/features/index/pages/index.templ b/features/index/pages/index.templ index 41e727f..b718d20 100644 --- a/features/index/pages/index.templ +++ b/features/index/pages/index.templ @@ -118,7 +118,7 @@ templ indexAuthed(v IndexView) { -

▸ tap to flip

+

▸ tap image to show QR code

if v.QRConnectURL != "" { } + @layouts.CameraScanButton()
if v.DisplayName != "" {

{ v.DisplayName }

@@ -258,9 +259,6 @@ templ indexGuest() { you{ "@" }cascadiajs:~$ whoami
-
- a person at a conference. you just met someone interesting and now their handle is gone. -
Find your people in the Atmosphere. diff --git a/features/index/pages/index_templ.go b/features/index/pages/index_templ.go index e109f49..4d653ea 100644 --- a/features/index/pages/index_templ.go +++ b/features/index/pages/index_templ.go @@ -241,7 +241,7 @@ func indexAuthed(v IndexView) templ.Component { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

▸ tap to flip

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

▸ tap image to show QR code

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -264,77 +264,85 @@ func indexAuthed(v IndexView) templ.Component { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = layouts.CameraScanButton().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if v.DisplayName != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(v.DisplayName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 135, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 136, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

(no display name)

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

(no display name)

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } if v.Handle != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs("@" + v.Handle) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 140, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 141, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if v.ConnectURL != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
connect link (for testing): ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
connect link (for testing): ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(v.ConnectURL) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 148, Col: 77} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 149, Col: 77} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -346,7 +354,7 @@ func indexAuthed(v IndexView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -354,7 +362,7 @@ func indexAuthed(v IndexView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -393,33 +401,33 @@ func indexEventCard(ev *IndexEventCard) templ.Component { } ctx = templ.ClearChildren(ctx) if ev != nil { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
checked in · live now

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\" class=\"home-event-card\" aria-label=\"current event\" style=\"text-decoration:none;color:inherit\">
checked in · live now

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(ev.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 180, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 181, Col: 42} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -427,13 +435,13 @@ func indexEventCard(ev *IndexEventCard) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(ev.Location) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 183, Col: 20} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 184, Col: 20} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " ·  ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, " ·  ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -441,31 +449,31 @@ func indexEventCard(ev *IndexEventCard) templ.Component { var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(ev.StartTime) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 185, Col: 20} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 186, Col: 20} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, " → ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, " → ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(ev.EndTime) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 185, Col: 39} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 186, Col: 39} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "

→
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

→") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -496,84 +504,84 @@ func indexRecentConnections(conns []RecentConnection) templ.Component { templ_7745c5c3_Var18 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -672,46 +680,46 @@ func indexGuest() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
zsh
you") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
zsh
you") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var28 string templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs("@") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 258, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 259, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "cascadiajs:~$ whoami
a person at a conference. you just met someone interesting and now their handle is gone.
Find your people in the Atmosphere.

atmo.quest 

An event companion ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "cascadiajs:~$ whoami

Find your people in the Atmosphere.

atmo.quest 

An event companion ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var29 string templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs("for") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 269, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 267, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, " the open social web. Scan a QR, write a note, leave with a real follow-up list. Your data goes to your repo, not ours.

quest_log.md

Side Quests ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, " the open social web. Scan a QR, write a note, leave with a real follow-up list. Your data goes to your repo, not ours.

quest_log.md

Side Quests ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs("// available") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 291, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `features/index/pages/index.templ`, Line: 289, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "

◆
Make your first connection
Scan someone's QR code. A quest.atmo.connection record gets written to your PDS. They get one too. That's the protocol bit.
+1 badge first connect
◆
Check in to an event
Drops a checkin record dated June 1–2, 2026. You'll find it in your repo forever.
+1 badge attendee
◆
Take a note while it's fresh
Private notes, attached to each connection. Read them Monday morning when you forget who Sarah was.
unlocks with first connection
◆
Help unlock the conference rewards
As more people join, new things appear — leaderboard, interest matching, end-of-conf stats. Earned together.
group quest see /unlocks
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
◆
Make your first connection
Scan someone's QR code. A quest.atmo.connection record gets written to your PDS. They get one too. That's the protocol bit.
+1 badge first connect
◆
Check in to an event
Drops a checkin record dated June 1–2, 2026. You'll find it in your repo forever.
+1 badge attendee
◆
Take a note while it's fresh
Private notes, attached to each connection. Read them Monday morning when you forget who Sarah was.
unlocks with first connection
◆
Help unlock the conference rewards
As more people join, new things appear — leaderboard, interest matching, end-of-conf stats. Earned together.
group quest see /unlocks
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } -- 2.51.2