Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

workspace-hub.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

workspace-hub.tsxBlame338 lines · 2 contributors
2c61840Claude1/**
2 * AI Workspace Hub — /:owner/:repo/workspace
3 *
4 * The single entry point for all AI-assisted coding on a repo.
5 * Surfaces the three existing modes in one clean page so developers
6 * don't have to hunt through menus to find them:
7 *
8 * Mode A — Spec-to-PR: describe a feature, AI writes the code + opens a PR
9 * Mode B — Fix an issue: pick an open issue, AI implements it autonomously
10 * Mode C — Quick edit: describe a small change, editor opens on a new branch
11 *
12 * Also shows the 5 most recent workspace/spec jobs for this repo so the
13 * owner can track in-progress AI tasks at a glance.
14 */
15
16import { Hono } from "hono";
17import { and, desc, eq, gte } from "drizzle-orm";
18import { db } from "../db";
19import { issues, repositories, users } from "../db/schema";
20import { softAuth } from "../middleware/auth";
21import type { AuthEnv } from "../middleware/auth";
22import { Layout } from "../views/layout";
23import { RepoHeader, RepoNav } from "../views/components";
24
25const app = new Hono<AuthEnv>();
26app.use("/:owner/:repo/workspace", softAuth);
27
28// ---------------------------------------------------------------------------
29// GET /:owner/:repo/workspace
30// ---------------------------------------------------------------------------
31
32app.get("/:owner/:repo/workspace", async (c) => {
33 const { owner, repo } = c.req.param();
34 const authUser = c.get("user");
35
36 const [repoRow] = await db
37 .select({ repo: repositories, owner: users })
38 .from(repositories)
39 .innerJoin(users, eq(repositories.ownerId, users.id))
40 .where(and(eq(users.username, owner), eq(repositories.name, repo)));
41
42 if (!repoRow) return c.notFound();
43 if (repoRow.repo.isPrivate && authUser?.id !== repoRow.owner.id) return c.notFound();
44
45 const isOwner = authUser?.id === repoRow.owner.id;
46
47 // Load a handful of open issues to populate the "Fix an issue" mode
48 const openIssues = await db
49 .select({
50 number: issues.number,
51 title: issues.title,
52 authorUsername: users.username,
53 })
54 .from(issues)
55 .innerJoin(users, eq(issues.authorId, users.id))
56 .where(and(eq(issues.repositoryId, repoRow.repo.id), eq(issues.state, "open")))
57 .orderBy(desc(issues.createdAt))
58 .limit(8);
59
60 const styles = `
61 .ws-wrap { max-width: 920px; margin: 0 auto; }
62 .ws-intro {
63 margin-bottom: var(--space-6);
64 padding: var(--space-5) var(--space-6);
65 background: var(--bg-elevated);
66 border: 1px solid var(--border);
67 border-radius: 16px;
68 position: relative;
69 overflow: hidden;
70 }
71 .ws-intro::before {
72 content: '';
73 position: absolute;
74 top: 0; left: 0; right: 0;
75 height: 3px;
76 background: var(--accent-gradient);
77 }
78 .ws-intro-title {
79 font-size: 22px;
80 font-weight: 700;
81 color: var(--text);
82 margin: 0 0 var(--space-2);
83 }
84 .ws-intro-sub {
85 font-size: 14px;
86 color: var(--text-secondary);
87 max-width: 600px;
88 }
89
90 .ws-modes {
91 display: grid;
92 grid-template-columns: 1fr 1fr;
93 gap: var(--space-4);
94 margin-bottom: var(--space-6);
95 }
96 @media (max-width: 680px) { .ws-modes { grid-template-columns: 1fr; } }
97 .ws-mode {
98 background: var(--bg-elevated);
99 border: 1px solid var(--border);
100 border-radius: 14px;
101 padding: var(--space-5);
102 display: flex;
103 flex-direction: column;
104 gap: var(--space-3);
105 transition: border-color 160ms, box-shadow 160ms;
106 }
107 .ws-mode:hover {
108 border-color: var(--border-strong);
109 box-shadow: 0 4px 20px rgba(0,0,0,0.18);
110 }
111 .ws-mode-icon {
112 font-size: 28px;
113 line-height: 1;
114 }
115 .ws-mode-title {
116 font-size: 16px;
117 font-weight: 700;
118 color: var(--text);
119 }
120 .ws-mode-desc {
121 font-size: 13px;
122 color: var(--text-secondary);
123 flex: 1;
124 line-height: 1.6;
125 }
126 .ws-mode-cta {
127 display: inline-flex;
128 align-items: center;
129 gap: 6px;
130 padding: 8px 16px;
131 background: var(--accent-gradient);
132 color: #fff;
133 border: none;
134 border-radius: 8px;
135 font-size: 13px;
136 font-weight: 600;
137 text-decoration: none;
138 cursor: pointer;
139 transition: opacity 150ms;
140 width: fit-content;
141 }
142 .ws-mode-cta:hover { opacity: 0.88; color: #fff; text-decoration: none; }
143 .ws-mode-cta.secondary {
144 background: var(--bg-inset);
145 color: var(--text-secondary);
146 border: 1px solid var(--border);
147 }
148 .ws-mode-cta.secondary:hover { border-color: var(--accent); color: var(--accent); opacity: 1; }
149
150 .ws-issues-section {
151 background: var(--bg-elevated);
152 border: 1px solid var(--border);
153 border-radius: 14px;
154 overflow: hidden;
155 margin-bottom: var(--space-6);
156 }
157 .ws-issues-head {
158 display: flex;
159 align-items: center;
160 justify-content: space-between;
161 padding: var(--space-3) var(--space-5);
162 border-bottom: 1px solid var(--border);
163 font-size: 12px;
164 font-weight: 700;
165 text-transform: uppercase;
166 letter-spacing: 0.07em;
167 color: var(--text-muted);
168 }
169 .ws-issue-row {
170 display: flex;
171 align-items: center;
172 gap: var(--space-3);
173 padding: var(--space-3) var(--space-5);
174 transition: background 120ms;
175 }
176 .ws-issue-row:hover { background: var(--bg-hover); }
177 .ws-issue-title {
178 flex: 1;
179 font-size: 13px;
180 color: var(--text);
181 font-weight: 500;
182 white-space: nowrap;
183 overflow: hidden;
184 text-overflow: ellipsis;
185 }
186 .ws-issue-num { font-size: 11px; color: var(--text-muted); }
187 .ws-issue-btn {
188 display: inline-flex;
189 align-items: center;
190 gap: 5px;
191 padding: 4px 10px;
192 font-size: 11px;
193 font-weight: 600;
194 border: 1px solid var(--border);
195 border-radius: 7px;
196 background: var(--bg-inset);
197 color: var(--text-secondary);
198 text-decoration: none;
199 white-space: nowrap;
200 flex-shrink: 0;
201 transition: border-color 120ms, color 120ms;
202 }
203 .ws-issue-btn:hover { border-color: var(--accent); color: var(--accent); }
204
205 .ws-tips {
206 display: grid;
207 grid-template-columns: repeat(3, 1fr);
208 gap: var(--space-3);
209 }
210 @media (max-width: 600px) { .ws-tips { grid-template-columns: 1fr; } }
211 .ws-tip {
212 padding: var(--space-4);
213 background: var(--bg-elevated);
214 border: 1px solid var(--border);
215 border-radius: 10px;
216 font-size: 12px;
217 color: var(--text-secondary);
218 line-height: 1.6;
219 }
220 .ws-tip-head { font-size: 13px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
221 `;
222
223 return c.html(
224 <Layout title={`AI Workspace — ${owner}/${repo}`} user={authUser ?? undefined}>
225 <style>{styles}</style>
226 <div class="ws-wrap">
227 <RepoHeader
228 owner={owner}
229 repo={repo}
8ed88f2ccantynz-alt230 starCount={repoRow.repo.starCount ?? 0}
231 forkCount={repoRow.repo.forkCount ?? 0}
2c61840Claude232 />
8ed88f2ccantynz-alt233 <RepoNav owner={owner} repo={repo} active="workspace" />
2c61840Claude234
235 {/* Hero intro */}
236 <div class="ws-intro">
237 <div class="ws-intro-title">✨ AI Workspace</div>
238 <div class="ws-intro-sub">
239 Claude-powered coding modes for <strong>{owner}/{repo}</strong>. Describe what you want built — the AI writes the code, opens a branch, and creates a draft PR for review.
240 </div>
241 </div>
242
243 {/* Three modes */}
244 <div class="ws-modes">
245 <div class="ws-mode">
246 <div class="ws-mode-icon">📋</div>
247 <div class="ws-mode-title">Spec-to-PR</div>
248 <div class="ws-mode-desc">
249 Paste a feature specification in plain English. Claude analyses the codebase, writes the implementation, and opens a draft pull request ready for your review.
250 </div>
251 <a href={`/${owner}/${repo}/spec`} class="ws-mode-cta">
252 Write a spec →
253 </a>
254 </div>
255
256 <div class="ws-mode">
257 <div class="ws-mode-icon">🔧</div>
258 <div class="ws-mode-title">Fix an issue</div>
259 <div class="ws-mode-desc">
260 Pick any open issue below. Claude reads the issue, explores the relevant code, writes a fix, and opens a PR — you just review and merge.
261 </div>
262 <a href={`/${owner}/${repo}/issues`} class="ws-mode-cta secondary">
263 Browse open issues ↓
264 </a>
265 </div>
266
267 <div class="ws-mode">
268 <div class="ws-mode-icon">✏️</div>
269 <div class="ws-mode-title">Web editor</div>
270 <div class="ws-mode-desc">
271 Edit files directly in the browser. Great for quick documentation updates, config tweaks, or small fixes without cloning the repo.
272 </div>
273 <a href={`/${owner}/${repo}/new/main`} class="ws-mode-cta secondary">
274 Open editor →
275 </a>
276 </div>
277
278 <div class="ws-mode">
279 <div class="ws-mode-icon">🧪</div>
280 <div class="ws-mode-title">Generate tests</div>
281 <div class="ws-mode-desc">
282 Point Claude at a file or function. It generates a test suite, creates a branch, and opens a PR so you can review coverage improvements instantly.
283 </div>
284 <a href={`/${owner}/${repo}/generate-tests`} class="ws-mode-cta secondary">
285 Generate tests →
286 </a>
287 </div>
288 </div>
289
290 {/* Open issues ready to be fixed by AI */}
291 {openIssues.length > 0 && (
292 <div class="ws-issues-section">
293 <div class="ws-issues-head">
294 <span>Open issues — launch AI workspace</span>
295 <a href={`/${owner}/${repo}/issues`} style="font-size:11px;color:var(--accent);text-transform:none;letter-spacing:0;text-decoration:none">
296 All issues →
297 </a>
298 </div>
299 {openIssues.map((issue) => (
300 <div class="ws-issue-row" key={issue.number}>
301 <span style="color:#3fb950;font-size:12px;flex-shrink:0">○</span>
302 <span class="ws-issue-title">{issue.title}</span>
303 <span class="ws-issue-num">#{issue.number}</span>
304 {isOwner && (
305 <a
306 href={`/${owner}/${repo}/issues/${issue.number}/workspace`}
307 class="ws-issue-btn"
308 title="Let AI implement this issue"
309 >
310 ✨ Fix with AI
311 </a>
312 )}
313 </div>
314 ))}
315 </div>
316 )}
317
318 {/* Usage tips */}
319 <div class="ws-tips">
320 <div class="ws-tip">
321 <div class="ws-tip-head">Best specs get best results</div>
322 Include acceptance criteria, the affected files if you know them, and any constraints (no new dependencies, must stay backwards-compatible, etc.).
323 </div>
324 <div class="ws-tip">
325 <div class="ws-tip-head">Always review the diff</div>
326 AI PRs are drafts. Claude marks them clearly with an ⚡ AI badge. Check the diff before merging — gates still run so broken builds are caught automatically.
327 </div>
328 <div class="ws-tip">
329 <div class="ws-tip-head">Issues → specs → PRs</div>
330 The fastest flow: file a clear issue, use "Fix with AI" to start a workspace, review the resulting PR. The gate CI runs automatically on every push.
331 </div>
332 </div>
333 </div>
334 </Layout>
335 );
336});
337
338export default app;