fix(onboarding+scaffold): setup flow stops overclaiming — chips do what they say, new users see the push snippet #5549
3 changed files+142−21
Modifiedsrc/lib/first-repo-scaffold.ts+88−0View fileUnifiedSplit
@@ -28,6 +28,14 @@
2828export interface ScaffoldResult {
2929 readmeCommitted: boolean;
3030 workflowCommitted: boolean;
31 /**
32 * Starter-specific files actually written (e.g. LICENSE, .gitignore).
33 * Until 2026-08-27 the "README + MIT" and "Node + .gitignore" starter
34 * chips were behaviourally identical to plain "README" — the scaffold
35 * took no starter parameter, so the form described three distinct
36 * choices and delivered one (flow audit #10).
37 */
38 extraFilesCommitted: string[];
3139 /** Workflows discovered and upserted by the shared sync seam. */
3240 workflowsSynced: number;
3341 /** CI runs enqueued — this is what makes the repo show a green check. */
@@ -85,6 +93,62 @@ Every push is scanned, reviewed and gated automatically.
8593`;
8694}
8795
96/** Standard MIT text — the "README + MIT" starter's reason to exist. */
97function mitLicense(holder: string): string {
98 const year = new Date().getFullYear();
99 return `MIT License
100
101Copyright (c) ${year} ${holder}
102
103Permission is hereby granted, free of charge, to any person obtaining a copy
104of this software and associated documentation files (the "Software"), to deal
105in the Software without restriction, including without limitation the rights
106to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
107copies of the Software, and to permit persons to whom the Software is
108furnished to do so, subject to the following conditions:
109
110The above copyright notice and this permission notice shall be included in all
111copies or substantial portions of the Software.
112
113THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
114IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
115FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
116AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
117LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
118OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
119SOFTWARE.
120`;
121}
122
123/** The "Node + .gitignore" starter's files. */
124function nodeGitignore(): string {
125 return `node_modules/
126dist/
127build/
128coverage/
129*.log
130.env
131.env.*
132!.env.example
133.DS_Store
134`;
135}
136
137function nodePackageJson(repoName: string): string {
138 return (
139 JSON.stringify(
140 {
141 name: repoName.toLowerCase().replace(/[^a-z0-9-_.]/g, "-"),
142 version: "0.1.0",
143 private: true,
144 scripts: { test: 'echo "no tests yet"' },
145 },
146 null,
147 2
148 ) + "\n"
149 );
150}
151
88152/**
89153 * Injectable dependencies.
90154 *
@@ -136,12 +200,15 @@ export async function scaffoldFirstRepo(
136200 authorName: string;
137201 authorEmail: string;
138202 userId: string;
203 /** Which starter chip the user picked; plain "readme" when omitted. */
204 starter?: "readme" | "readme-mit" | "node";
139205 },
140206 injected?: ScaffoldDeps
141207): Promise<ScaffoldResult> {
142208 const result: ScaffoldResult = {
143209 readmeCommitted: false,
144210 workflowCommitted: false,
211 extraFilesCommitted: [],
145212 workflowsSynced: 0,
146213 runsEnqueued: 0,
147214 filesIndexed: 0,
@@ -181,6 +248,27 @@ export async function scaffoldFirstRepo(
181248 );
182249 }
183250
251 // Starter-specific files — the difference between the three chips.
252 const extras: Array<{ path: string; body: string; message: string }> =
253 opts.starter === "readme-mit"
254 ? [{ path: "LICENSE", body: mitLicense(opts.authorName), message: "Add MIT license" }]
255 : opts.starter === "node"
256 ? [
257 { path: ".gitignore", body: nodeGitignore(), message: "Add Node .gitignore" },
258 { path: "package.json", body: nodePackageJson(opts.repoName), message: "Add package.json" },
259 ]
260 : [];
261 for (const extra of extras) {
262 try {
263 headSha = await write(extra.path, extra.body, extra.message);
264 result.extraFilesCommitted.push(extra.path);
265 } catch (err) {
266 result.errors.push(
267 `${extra.path}: ${err instanceof Error ? err.message : String(err)}`
268 );
269 }
270 }
271
184272 try {
185273 headSha = await write(
186274 ".gluecron/workflows/ci.yml",
Modifiedsrc/routes/onboarding.tsx+49−21View fileUnifiedSplit
@@ -490,7 +490,10 @@ const gettingStartedHandler = async (c: any) => {
490490 {
491491 n: 1,
492492 title: "Create a repository",
493 desc: "Green-ecosystem defaults, branch protection, labels, CODEOWNERS — wired on day one.",
493 // No CODEOWNERS claim: bootstrap wires protection + labels + a
494 // welcome issue, but never writes a CODEOWNERS file (the flow
495 // audit caught the overclaim; only a parser exists).
496 desc: "Green-ecosystem defaults, branch protection, labels — wired on day one.",
494497 cta: { href: "/new", label: "Create repo", primary: true },
495498 skip: null,
496499 icon: <IconConnect />,
@@ -509,7 +512,9 @@ const gettingStartedHandler = async (c: any) => {
509512 n: 3,
510513 title: "Run the gates",
511514 desc: "Push a commit and watch GateTest, AI review, and CI run automatically.",
512 cta: { href: "/explore", label: "Browse repos", primary: false },
515 // Label matches the destination — the button used to say
516 // "Browse repos" under a card about pushing commits.
517 cta: { href: "/explore", label: "See gates on live repos", primary: false },
513518 skip: { href: "/dashboard", label: "Skip" },
514519 icon: <IconRun />,
515520 done: false,
@@ -522,15 +527,17 @@ const gettingStartedHandler = async (c: any) => {
522527 skip: { href: "/dashboard", label: "Skip" },
523528 icon: <IconShip />,
524529 done: false,
530 optional: true,
525531 },
526532 {
527533 n: 5,
528534 title: "Ship to production",
529535 desc: "Configure auto-merge + deploy webhooks. Your push lands live in ~25 seconds.",
530 cta: { href: "/help", label: "Read the guide", primary: false },
536 cta: { href: "/help", label: "Browse help topics", primary: false },
531537 skip: { href: "/dashboard", label: "Skip" },
532538 icon: <IconShip />,
533539 done: false,
540 optional: true,
534541 },
535542 ]
536543 : [
@@ -577,15 +584,17 @@ const gettingStartedHandler = async (c: any) => {
577584 skip: { href: "/dashboard", label: "Skip" },
578585 icon: <IconShip />,
579586 done: false,
587 optional: true,
580588 },
581589 {
582590 n: 5,
583591 title: "Ship to production",
584592 desc: "Configure auto-merge + deploy webhooks. Your push lands live in ~25 seconds.",
585 cta: { href: "/help", label: "Read the guide", primary: false },
593 cta: { href: "/help", label: "Browse help topics", primary: false },
586594 skip: { href: "/dashboard", label: "Skip" },
587595 icon: <IconShip />,
588596 done: false,
597 optional: true,
589598 },
590599 ];
591600
@@ -625,6 +634,14 @@ const gettingStartedHandler = async (c: any) => {
625634 <div class="onb-step-head">
626635 <span class="onb-step-num">{s.done ? "✓" : s.n}</span>
627636 <span class="onb-step-icon" aria-hidden="true">{s.icon}</span>
637 {/* Steps 4/5 have no completion detector — they used to
638 render as silently un-finishable, sitting "incomplete"
639 directly under the "You're all set." card. Say so. */}
640 {(s as { optional?: boolean }).optional && (
641 <span class="onb-step-optional" style="margin-left:auto;font-size:11px;font-weight:600;letter-spacing:0.05em;text-transform:uppercase;color:var(--text-muted)">
642 Optional
643 </span>
644 )}
628645 </div>
629646 <h3 class="onb-step-title">{s.title}</h3>
630647 <p class="onb-step-desc">{s.desc}</p>
@@ -652,21 +669,25 @@ const gettingStartedHandler = async (c: any) => {
652669 ))}
653670 </div>
654671
655 {/* ─── Push snippet (only once the user has at least one repo) ─── */}
656 {!firstRun && (
657 <section class="onb-section">
658 <header class="onb-section-head">
659 <h3 class="onb-section-title">Push an existing project</h3>
660 <p class="onb-section-sub">
661 Add Gluecron as a git remote and push. We'll pick up the history on the first sync.
662 </p>
663 </header>
664 <div class="onb-section-body">
665 <pre class="onb-code">{`git remote add gluecron ${config.appBaseUrl}/${user.username}/your-repo.git
672 {/* ─── Push snippet ─── */}
673 {/* Used to be gated on `!firstRun` — shown only to users who
674 already had repos, hidden from the brand-new users who needed
675 it most (flow audit #9: inverted condition). Always render;
676 firstRun gets a note to create the repo first. */}
677 <section class="onb-section">
678 <header class="onb-section-head">
679 <h3 class="onb-section-title">Push an existing project</h3>
680 <p class="onb-section-sub">
681 {firstRun
682 ? "Create the repository above first, then add Gluecron as a remote and push:"
683 : "Add Gluecron as a git remote and push. We'll pick up the history on the first sync."}
684 </p>
685 </header>
686 <div class="onb-section-body">
687 <pre class="onb-code">{`git remote add gluecron ${config.appBaseUrl}/${user.username}/your-repo.git
666688git push -u gluecron main`}</pre>
667 </div>
668 </section>
669 )}
689 </div>
690 </section>
670691
671692 {/* ─── All-done empty state — celebration card ─── */}
672693 {allDone && (
@@ -674,9 +695,14 @@ git push -u gluecron main`}</pre>
674695 <div class="onb-empty-orb" aria-hidden="true" />
675696 <div class="onb-empty-inner">
676697 <span class="onb-empty-glyph" aria-hidden="true"><IconSparkle /></span>
677 <h2 class="onb-empty-title">You're all set.</h2>
698 {/* "Core", not "all" — steps 4/5 are optional with no
699 completion detector; the old copy declared everything
700 done while two cards above sat visibly unfinished. */}
701 <h2 class="onb-empty-title">Core setup complete.</h2>
678702 <p class="onb-empty-sub">
679 Setup complete. Start building, browsing, or invite a teammate.
703 Repos, keys, and tokens are ready. The optional steps above
704 are there whenever you want them — start building, browsing,
705 or invite a teammate.
680706 </p>
681707 <div class="onb-empty-actions">
682708 <a href="/dashboard" class="onb-btn onb-btn-primary">Open dashboard</a>
@@ -690,7 +716,9 @@ git push -u gluecron main`}</pre>
690716 <div class="onb-foot">
691717 <a href="/dashboard">Skip to dashboard →</a>
692718 <div style="margin-top:10px">
693 Need help? See the <a href="/api/docs">API docs</a> or press{" "}
719 {/* /help, not /api/docs — an API reference is the wrong first
720 stop for someone who hasn't pushed yet. */}
721 Need help? See the <a href="/help">help center</a> or press{" "}
694722 <span class="onb-foot-kbd">?</span> for shortcuts.
695723 </div>
696724 </div>
Modifiedsrc/routes/web.tsx+5−0View fileUnifiedSplit
@@ -2277,6 +2277,11 @@ web.post("/new", requireAuth, async (c) => {
22772277 authorName: user.username,
22782278 authorEmail: user.email || `${user.username}@users.noreply.gluecron.com`,
22792279 userId: user.id,
2280 // The chip the user actually clicked — for as long as the chips
2281 // existed this was never passed, so "README + MIT" wrote no
2282 // LICENSE and "Node + .gitignore" wrote no .gitignore.
2283 starter:
2284 starter === "readme-mit" || starter === "node" ? starter : "readme",
22802285 });
22812286 } catch (err) {
22822287 console.error("[new-repo] scaffold failed:", err);
22832288
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts