diff --git a/public/sessions-scripts.js b/public/sessions-scripts.js
index f13f7ee..4a69082 100644
--- a/public/sessions-scripts.js
+++ b/public/sessions-scripts.js
@@ -735,12 +735,20 @@ async function openAttendeesDrawer(eventId, eventName) {
if (res.ok && data.success) {
const attendees = data.attendees || [];
- const claimedElem = document.getElementById("guestDrawerClaimed");
- if (claimedElem) claimedElem.textContent = attendees.length;
-
if (data.event) {
+ const claimedElem = document.getElementById("guestDrawerClaimed");
+ if (claimedElem) {
+ claimedElem.textContent = data.event.seats_claimed !== undefined
+ ? data.event.seats_claimed
+ : attendees.length;
+ }
+
const maxElem = document.getElementById("guestDrawerMax");
- if (maxElem) maxElem.textContent = data.event.max_seats;
+ if (maxElem) {
+ maxElem.textContent = Number(data.event.max_seats) > 0
+ ? data.event.max_seats
+ : "∞";
+ }
const expiresAtElem = document.getElementById("guestDrawerExpiresAt");
if (expiresAtElem && data.event.expires_at) {
diff --git a/src/features/events/events_actions_routes.ts b/src/features/events/events_actions_routes.ts
index 5703cc1..3e8daac 100644
--- a/src/features/events/events_actions_routes.ts
+++ b/src/features/events/events_actions_routes.ts
@@ -216,7 +216,11 @@ eventsActionsRoutes.get("/api/events/:id/attendees", async (c) => {
success: true,
attendees: data.attendees,
event: {
+ id: data.event.id,
+ name: data.event.name,
+ slug: data.event.slug,
max_seats: data.event.max_seats,
+ seats_claimed: data.event.seats_claimed,
expires_at: data.event.expires_at,
},
});
diff --git a/src/features/events/queries.ts b/src/features/events/queries.ts
index bb33fea..ac2c17a 100644
--- a/src/features/events/queries.ts
+++ b/src/features/events/queries.ts
@@ -237,18 +237,22 @@ export async function getEventWithAttendees(
isGlobalAdmin: boolean,
) {
const event = await sqlWrapper.sql`
- SELECT id, slug, max_seats, expires_at FROM event_passes
+ SELECT id, name, slug, max_seats, seats_claimed, expires_at FROM event_passes
WHERE id = ${eventId}
AND (created_by = ${userId} OR ${isGlobalAdmin})
`.then((res: any) => res[0]);
if (!event) return null;
+ const shortId = String(event.id).split("-")[0];
+ const shortPattern = `guest_${shortId}_%`;
+
const attendees = await sqlWrapper.sql`
SELECT s.id, s.label, s.is_paused, s.created_at, s.expires_at, s.last_activity_at, s.last_activity_action, u.username, u.display_name
FROM sessions s
JOIN users u ON s.user_id = u.id
WHERE u.event_pass_id = ${eventId}
+ OR u.username LIKE ${shortPattern}
ORDER BY s.created_at DESC
`;
diff --git a/src/features/sessions/sessions.test.tsx b/src/features/sessions/sessions.test.tsx
index f91cda4..48cd533 100644
--- a/src/features/sessions/sessions.test.tsx
+++ b/src/features/sessions/sessions.test.tsx
@@ -10,6 +10,7 @@ import {
EventCockpitDeckFragment,
WorkshopPassDrawerFragment,
} from "../events/fragments.tsx";
+import { AppLaunchpadPageFragment } from "../dashboard/launchpad_fragments.tsx";
Deno.test("[Sessions] GET /dashboard unauthenticated redirects to /login", async () => {
const req = new Request("http://localhost/dashboard");
@@ -155,3 +156,42 @@ Deno.test("[Sessions] GET /dashboard/passkeys unauthenticated redirects to /logi
assertEquals(res.status, 302);
assertEquals(res.headers.get("location"), "/login");
});
+
+Deno.test("[Sessions] AppLaunchpadPageFragment renders authorized workload cards and roles", () => {
+ const mockApps = [
+ {
+ id: "app-1",
+ name: "ed-droid",
+ description: "Autonomous Agent UI",
+ domain: "ed.atyg.org",
+ role: "Guest (Viewer)",
+ },
+ {
+ id: "app-2",
+ name: "grafana",
+ description: "Metrics Dashboard",
+ domain: "grafana.atyg.org",
+ role: "Admin",
+ },
+ ];
+
+ const html = ;
+ const str = String(html);
+ assertStringIncludes(str, "Application Launchpad");
+ assertStringIncludes(str, "ed-droid");
+ assertStringIncludes(str, "ed.atyg.org");
+ assertStringIncludes(str, "Guest (Viewer)");
+ assertStringIncludes(str, "https://ed.atyg.org");
+ assertStringIncludes(str, "grafana");
+ assertStringIncludes(str, "badge-success");
+});
+
+Deno.test("[Sessions] AppLaunchpadPageFragment renders empty state when no apps authorized", () => {
+ const html = ;
+ const str = String(html);
+ assertStringIncludes(str, "No Authorized Applications");
+ assertStringIncludes(
+ str,
+ "You do not have active role grants for any workloads.",
+ );
+});