Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commitf1dc38bunknown_key

Make Claude web sessions customer-facing at /:owner/:repo/claude

Make Claude web sessions customer-facing at /:owner/:repo/claude

- Switch session list from listSessionsForRepo (all users) to
  listSessionsForUser (filtered by ownerUserId + repositoryId), so
  each user sees only their own sessions (privacy isolation).
- Add listSessionsForUser() to src/lib/claude-web-session.ts with
  correct orderBy(lastActiveAt) and dual-column where clause.
- Improve session-list UI: status badges with colour coding (cold/
  running/ready/failed), reverse-chronological ordering, better copy.
- Add "✨ Claude AI" sidebar card to the repo home page
  (src/routes/web.tsx) — shows "Claude Code sessions" and "Ask AI"
  links for authenticated users. Avoids touching the locked RepoNav.
- No admin gate — the gate() helper already enforces requireAuth +
  minimum read access via resolveRepoAccess. AI response is triggered
  by the SSE stream at GET /:owner/:repo/claude/:sessionId/stream,
  which spawns the `claude` CLI with --print --output-format
  stream-json in the session's working-dir clone of the repo.

https://claude.ai/code/session_01DzJMTFASjMHt2f5ze4cNLR
Claude committed on June 6, 2026Parent: 30dcfbe
3 files changed+9618f1dc38b3f2d5e786861ecbfe390a188cdab0e985
3 changed files+96−18
Modifiedsrc/lib/claude-web-session.ts+23−0View fileUnifiedSplit
392392 .limit(limit);
393393}
394394
395/**
396 * List sessions for a specific user in a specific repo, ordered by most
397 * recently active first. Used by the customer-facing /:owner/:repo/claude
398 * page so each user sees only their own sessions.
399 */
400export async function listSessionsForUser(
401 repositoryId: string,
402 ownerUserId: string,
403 limit = 50
404): Promise<ClaudeWebSession[]> {
405 return db
406 .select()
407 .from(claudeWebSessions)
408 .where(
409 and(
410 eq(claudeWebSessions.repositoryId, repositoryId),
411 eq(claudeWebSessions.ownerUserId, ownerUserId)
412 )
413 )
414 .orderBy(claudeWebSessions.lastActiveAt)
415 .limit(limit);
416}
417
395418export async function deleteSession(id: string): Promise<void> {
396419 await db.delete(claudeWebSessions).where(eq(claudeWebSessions.id, id));
397420}
Modifiedsrc/routes/claude-web.tsx+53−18View fileUnifiedSplit
3232 ensureWorkdir,
3333 getSession,
3434 listMessages,
35 listSessionsForRepo,
35 listSessionsForUser,
3636 runTurn,
3737 touchSession,
3838} from "../lib/claude-web-session";
8181 const g = await gate(c);
8282 if (g instanceof Response) return g;
8383 const user = c.get("user")!;
84 const sessions = await listSessionsForRepo(g.repoId);
84 // Show only the current user's sessions for this repo (privacy isolation).
85 const sessions = await listSessionsForUser(g.repoId, g.userId);
86
87 const statusBadge = (status: string) => {
88 const colors: Record<string, string> = {
89 cold: "#374151",
90 running: "#1e40af",
91 ready: "#14532d",
92 failed: "#7f1d1d",
93 };
94 const bg = colors[status] ?? "#374151";
95 return (
96 <span
97 style={`background:${bg};color:#e5e7eb;font-size:11px;padding:2px 7px;border-radius:10px;font-family:ui-monospace,monospace;vertical-align:middle`}
98 >
99 {status}
100 </span>
101 );
102 };
85103
86104 return c.html(
87105 <Layout title={`Claude — ${g.ownerName}/${g.repoName}`} user={user}>
91109 ← {g.ownerName}/{g.repoName}
92110 </a>
93111 </p>
94 <h1 style="margin:0 0 6px;font-size:22px">Claude on this repo</h1>
95 <p style="margin:0 0 18px;color:#9ca3af;font-size:14px">
96 iPad-friendly Claude Code sessions on a per-session clone of the repo.
112 <h1 style="margin:0 0 4px;font-size:22px">✨ Claude Code Sessions</h1>
113 <p style="margin:0 0 20px;color:#9ca3af;font-size:14px">
114 Browser-based Claude Code sessions on a live clone of this repo. Each session
115 persists its transcript so you can resume from any device.
97116 </p>
98117
99118 <form method="post" action={`/${g.ownerName}/${g.repoName}/claude`} style={card}>
100 <label style="display:block;font-size:13px;color:#9ca3af;margin-bottom:6px">
101 New session title
119 <label style="display:block;font-size:13px;color:#9ca3af;margin-bottom:8px;font-weight:600">
120 Start a new session
102121 </label>
103 <div style="display:flex;gap:8px">
104 <input name="title" placeholder="What you want to work on" style={inputStyle} />
105 <input name="branch" placeholder="main" style={inputStyle + ";max-width:160px"} />
106 <button type="submit" style={btn}>Start</button>
122 <div style="display:flex;gap:8px;flex-wrap:wrap">
123 <input
124 name="title"
125 placeholder="What do you want to work on?"
126 style={inputStyle + ";flex:1;min-width:180px"}
127 />
128 <input
129 name="branch"
130 placeholder="branch (default: main)"
131 style={inputStyle + ";max-width:200px"}
132 />
133 <button type="submit" style={btn}>
134 Start session →
135 </button>
107136 </div>
108137 </form>
109138
110139 <div style={card}>
111 <h2 style="margin:0 0 10px;font-size:15px;color:#cbd5e1">Sessions</h2>
140 <h2 style="margin:0 0 12px;font-size:15px;color:#cbd5e1">
141 Your sessions{sessions.length > 0 ? ` (${sessions.length})` : ""}
142 </h2>
112143 {sessions.length === 0 ? (
113 <p style="color:#6b7280;margin:0;font-size:14px">None yet.</p>
144 <p style="color:#6b7280;margin:0;font-size:14px">
145 No sessions yet. Start one above — Claude will clone the repo and
146 open a persistent conversation.
147 </p>
114148 ) : (
115149 <ul style="list-style:none;padding:0;margin:0">
116 {sessions.map((s) => (
117 <li style="padding:10px 0;border-top:1px solid #1f2937;display:flex;justify-content:space-between;align-items:center">
150 {[...sessions].reverse().map((s) => (
151 <li style="padding:12px 0;border-top:1px solid #1f2937;display:flex;justify-content:space-between;align-items:center;gap:12px">
118152 <a
119153 href={`/${g.ownerName}/${g.repoName}/claude/${s.id}`}
120 style="color:#7aa2f7;text-decoration:none;font-weight:600"
154 style="color:#7aa2f7;text-decoration:none;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
121155 >
122156 {s.title}
123157 </a>
124 <span style="color:#6b7280;font-size:12px;font-family:ui-monospace,monospace">
125 {s.branch} · {s.status}
158 <span style="display:flex;align-items:center;gap:8px;flex-shrink:0;color:#6b7280;font-size:12px;font-family:ui-monospace,monospace">
159 {s.branch}
160 {statusBadge(s.status)}
126161 </span>
127162 </li>
128163 ))}
Modifiedsrc/routes/web.tsx+20−0View fileUnifiedSplit
35973597 </div>
35983598 )}
35993599 </div>
3600 {/* Claude AI — quick-access card for authenticated users */}
3601 {user && (
3602 <div class="repo-home-side-card" style="margin-top:12px">
3603 <h3 class="repo-home-side-title" style="margin-bottom:10px">
3604 ✨ Claude AI
3605 </h3>
3606 <a
3607 href={`/${owner}/${repo}/claude`}
3608 style="display:block;background:#1f6feb;color:#fff;text-align:center;padding:8px 14px;border-radius:6px;text-decoration:none;font-size:14px;font-weight:600;margin-bottom:8px"
3609 >
3610 Claude Code sessions
3611 </a>
3612 <a
3613 href={`/${owner}/${repo}/ask`}
3614 style="display:block;color:#9ca3af;text-align:center;padding:6px 14px;border-radius:6px;text-decoration:none;font-size:13px;border:1px solid #1f2937"
3615 >
3616 Ask AI about this repo
3617 </a>
3618 </div>
3619 )}
36003620 </aside>
36013621 </div>
36023622 <script
36033623