@bongos/core 1.17.3 → 1.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/.bongos-core.json +212 -332
  2. package/.devcontainer/healthcheck.sh +0 -0
  3. package/.devcontainer/init-firewall.sh +0 -0
  4. package/.devcontainer/post-create.sh +0 -0
  5. package/GOVERNANCE.md +1 -1
  6. package/README.md +26 -22
  7. package/bin/bongos.js +0 -0
  8. package/config/branding.neutral.json +5 -1
  9. package/docker-entrypoint.sh +0 -0
  10. package/docs/adr/0142-project-context-is-prose-only.md +41 -0
  11. package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
  12. package/docs/adr/README.md +6 -0
  13. package/docs/api/openapi.json +1630 -632
  14. package/docs/api-reference.md +50 -15
  15. package/docs/file-map.md +0 -18
  16. package/docs/onboarding/diagrams/05-versioning.mmd +4 -6
  17. package/docs/onboarding/diagrams/assertions.json +5 -9
  18. package/docs/recipes/packaging-the-core.md +13 -0
  19. package/docs/recipes/upgrading-the-core.md +5 -2
  20. package/migrations/039_newcomer_friendly_column.sql +5 -0
  21. package/migrations/102_remove_box_first_provision_approval.sql +4 -0
  22. package/migrations/core_192_github_oauth_tokens.sql +33 -0
  23. package/modules/hall-ui/public/builders.js +37 -0
  24. package/modules/hall-ui/public/goals-page.js +98 -176
  25. package/modules/hall-ui/public/goals.css +183 -0
  26. package/modules/hall-ui/public/goals.html +35 -261
  27. package/modules/hall-ui/public/goals.js +7 -2
  28. package/modules/hall-ui/public/hall-widgets.js +12 -4
  29. package/modules/hall-ui/public/my-projects.html +78 -0
  30. package/modules/hall-ui/public/my-projects.js +142 -0
  31. package/modules/hall-ui/public/scouting.html +45 -0
  32. package/modules/hall-ui/public/scouting.js +73 -0
  33. package/modules/hall-ui/public/settings.css +355 -0
  34. package/modules/hall-ui/public/settings.html +86 -414
  35. package/modules/hall-ui/public/settings.js +96 -35
  36. package/modules/hall-ui/public/style.css +31 -0
  37. package/modules/hall-ui/public/watch.css +184 -0
  38. package/modules/hall-ui/public/watch.html +111 -285
  39. package/modules/hall-ui/public/watch.js +59 -21
  40. package/modules/status-ui/public/explore.html +54 -0
  41. package/modules/status-ui/public/explore.js +67 -0
  42. package/package-lock.json +2 -2
  43. package/package.json +1 -1
  44. package/public-landing/create.html +292 -19
  45. package/public-landing/index.html +11 -76
  46. package/scripts/cron/style_guide_audit.sh +0 -0
  47. package/scripts/gds/api.js +0 -0
  48. package/scripts/gds/architect-audit.js +0 -0
  49. package/scripts/gds/audit-authorship.sh +0 -0
  50. package/scripts/gds/audit-replay.sh +0 -0
  51. package/scripts/gds/box.js +0 -0
  52. package/scripts/gds/capture.js +0 -0
  53. package/scripts/gds/cli-lib.js +11 -5
  54. package/scripts/gds/conductor-dispatch.js +0 -0
  55. package/scripts/gds/coreB-carve-proof.sh +0 -0
  56. package/scripts/gds/db-backup-nightly.sh +0 -0
  57. package/scripts/gds/db-backup-offsite.sh +0 -0
  58. package/scripts/gds/doctor.js +0 -0
  59. package/scripts/gds/grade-smoke.js +0 -0
  60. package/scripts/gds/hooks/pre-push.js +0 -0
  61. package/scripts/gds/install-git-hooks.js +0 -0
  62. package/scripts/gds/lint-migrations.js +102 -0
  63. package/scripts/gds/login.js +42 -10
  64. package/scripts/gds/main-audit.js +0 -0
  65. package/scripts/gds/mirror-redact.js +5 -0
  66. package/scripts/gds/oauth-secret.js +0 -0
  67. package/scripts/gds/onboarding-analyze.js +0 -0
  68. package/scripts/gds/onboarding-ship.js +0 -0
  69. package/scripts/gds/package-core.js +263 -4
  70. package/scripts/gds/paste-token.js +0 -0
  71. package/scripts/gds/portability-smoke-art.sh +0 -0
  72. package/scripts/gds/portability-smoke-memory.sh +0 -0
  73. package/scripts/gds/provision.js +270 -11
  74. package/scripts/gds/publish-manifest.js +15 -0
  75. package/scripts/gds/push-main.js +0 -0
  76. package/scripts/gds/render-ideas.js +0 -0
  77. package/scripts/gds/run-unit-tests.js +38 -14
  78. package/scripts/gds/security-baseline-check.sh +0 -0
  79. package/scripts/gds/session.js +0 -0
  80. package/scripts/gds/setup.js +41 -0
  81. package/scripts/gds/ship.js +126 -32
  82. package/scripts/gds/smoke-batch-claim.sh +0 -0
  83. package/scripts/gds/smoke-batch-release.sh +0 -0
  84. package/scripts/gds/smoke-box.sh +0 -0
  85. package/scripts/gds/smoke-claim-consistency.sh +0 -0
  86. package/scripts/gds/smoke-dependencies.sh +0 -0
  87. package/scripts/gds/smoke-write.sh +0 -0
  88. package/scripts/gds/upgrade.js +147 -5
  89. package/scripts/hall-preview/server.js +18 -1
  90. package/scripts/migrate.sh +10 -0
  91. package/scripts/migrate_cost_ledger.sh +0 -0
  92. package/scripts/render-diagrams.sh +0 -0
  93. package/scripts/security/dep-audit.js +0 -0
  94. package/scripts/setup-builder.js +0 -0
  95. package/scripts/setup-builder.sh +0 -0
  96. package/scripts/snapshots/maybe-render-on-deploy.sh +0 -0
  97. package/src/bongos/auth.js +362 -5
  98. package/src/bongos/db.js +66 -0
  99. package/src/bongos/github-repos.js +93 -0
  100. package/src/bongos/routes/auth.js +93 -11
  101. package/src/bongos/routes/github.js +90 -0
  102. package/src/bongos/routes/instance.js +15 -3
  103. package/src/bongos/routes.js +2 -0
  104. package/src/branding.js +17 -2
  105. package/src/instance-config.js +16 -0
  106. package/src/module-api.js +48 -1
  107. package/tests/branding.mjs +29 -23
  108. package/tests/github_repos_helpers.mjs +91 -0
  109. package/tests/github_token_store.mjs +121 -0
  110. package/tests/idp_device_exchange.mjs +105 -0
  111. package/tests/idp_federation.mjs +100 -0
  112. package/tests/migrations_forward_safe.mjs +86 -0
  113. package/tests/module_api.mjs +1 -1
  114. package/tests/package_core.mjs +246 -1
  115. package/tests/provision.mjs +226 -0
  116. package/tests/pruned_lockfile_integrity.mjs +79 -0
  117. package/tests/ship_ci_deploy.mjs +60 -0
  118. package/tests/upgrade.mjs +254 -5
  119. package/.gitignore +0 -91
  120. package/scripts/hall-preview/fixtures/access-requests.json +0 -1
  121. package/scripts/hall-preview/fixtures/achievements.json +0 -76
  122. package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
  123. package/scripts/hall-preview/fixtures/audit-log.json +0 -1
  124. package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -1
  125. package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
  126. package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
  127. package/scripts/hall-preview/fixtures/builders__roster.json +0 -1
  128. package/scripts/hall-preview/fixtures/goals.json +0 -502
  129. package/scripts/hall-preview/fixtures/goals__25.json +0 -96
  130. package/scripts/hall-preview/fixtures/goals__26.json +0 -67
  131. package/scripts/hall-preview/fixtures/goals__3.json +0 -103
  132. package/scripts/hall-preview/fixtures/goals__35.json +0 -49
  133. package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
  134. package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
  135. package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
  136. package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
  137. package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -1
  138. package/scripts/hall-preview/fixtures/inbox.json +0 -330
  139. package/scripts/hall-preview/fixtures/instance.json +0 -124
  140. package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
  141. package/scripts/hall-preview/fixtures/me.json +0 -476
  142. package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
  143. package/scripts/hall-preview/fixtures/override-requests.json +0 -1
  144. package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
  145. package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
  146. package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
  147. package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
  148. package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
  149. package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
  150. package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
  151. package/scripts/hall-preview/fixtures/public__grades.json +0 -50
  152. package/scripts/hall-preview/fixtures/public__progress.json +0 -124
  153. package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
  154. package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
  155. package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
  156. package/scripts/hall-preview/fixtures/security__docs.json +0 -1
  157. package/scripts/hall-preview/fixtures/security__reports.json +0 -1
  158. package/scripts/hall-preview/fixtures/sessions__search.json +0 -1
  159. package/scripts/hall-preview/fixtures/tasks.json +0 -2420
  160. package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
  161. package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
  162. package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
  163. package/scripts/hall-preview/fixtures/versions.json +0 -89
@@ -0,0 +1,54 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Explore — featured projects</title>
7
+ <style>
8
+ /* Self-contained so the showcase renders correctly whether or not the host
9
+ injects theme tokens. Warm Cloud Bongos defaults; overridable by the pack. */
10
+ :root {
11
+ --bg: #fff7ed; --card: #ffffff; --ink: #2a1f17; --ink-soft: #6b5d52;
12
+ --rule: #fed7aa; --accent: #ea580c;
13
+ }
14
+ * { box-sizing: border-box; }
15
+ body {
16
+ margin: 0; padding: 2.5rem 1.25rem 4rem;
17
+ background: var(--bg); color: var(--ink);
18
+ font-family: "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif;
19
+ line-height: 1.5;
20
+ }
21
+ .wrap { max-width: 960px; margin: 0 auto; }
22
+ header { text-align: center; margin-bottom: 2.5rem; }
23
+ h1 { font-size: 2rem; margin: 0 0 .35rem; letter-spacing: .01em; }
24
+ .sub { color: var(--ink-soft); margin: 0; }
25
+ .grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
26
+ .card {
27
+ background: var(--card); border: 1px solid var(--rule); border-radius: 14px;
28
+ padding: 1.1rem 1.2rem; text-decoration: none; color: inherit;
29
+ display: flex; flex-direction: column; gap: .4rem; transition: transform .12s ease, box-shadow .12s ease;
30
+ }
31
+ .card:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(234,88,12,.12); }
32
+ .card h2 { font-size: 1.15rem; margin: 0; }
33
+ .card p { margin: 0; color: var(--ink-soft); font-size: .95rem; }
34
+ .card .origin { color: var(--accent); font-size: .82rem; word-break: break-all; margin-top: auto; }
35
+ .card .art { width: 100%; height: 120px; object-fit: cover; border-radius: 10px; margin-bottom: .3rem; }
36
+ .empty, .err { text-align: center; color: var(--ink-soft); padding: 3rem 1rem; }
37
+ @media (prefers-color-scheme: dark) {
38
+ :root { --bg: #1a1410; --card: #241c16; --ink: #f5ece3; --ink-soft: #b9a998; --rule: #4a3728; }
39
+ }
40
+ </style>
41
+ </head>
42
+ <body>
43
+ <div class="wrap">
44
+ <header>
45
+ <h1>Explore</h1>
46
+ <p class="sub">Featured projects built on the platform.</p>
47
+ </header>
48
+ <div id="grid" class="grid" hidden></div>
49
+ <div id="empty" class="empty" hidden>No featured projects yet — check back soon.</div>
50
+ <div id="err" class="err" hidden>Couldn't load projects right now.</div>
51
+ </div>
52
+ <script src="/explore.js"></script>
53
+ </body>
54
+ </html>
@@ -0,0 +1,67 @@
1
+ // explore.js — the public Explore showcase (ADR 0141 §5, task #1002110). Fetches the
2
+ // hub's featured projects and renders cards. On a NON-hub instance (platform-identity
3
+ // off) the endpoint 404s / returns nothing, so the page shows a graceful empty state.
4
+ //
5
+ // Project fields come from the network, so every value is set via textContent /
6
+ // attribute assignment — never innerHTML — so a project name/tagline can't inject markup.
7
+ (function () {
8
+ 'use strict';
9
+ var grid = document.getElementById('grid');
10
+ var empty = document.getElementById('empty');
11
+ var err = document.getElementById('err');
12
+
13
+ function show(el) { if (el) el.hidden = false; }
14
+
15
+ // Only http(s) origins are linkified — never javascript:/data: etc.
16
+ function safeHref(origin) {
17
+ try {
18
+ var u = new URL(String(origin));
19
+ return (u.protocol === 'https:' || u.protocol === 'http:') ? u.toString() : null;
20
+ } catch (_) { return null; }
21
+ }
22
+
23
+ function card(p) {
24
+ var href = safeHref(p.origin);
25
+ var el = document.createElement(href ? 'a' : 'div');
26
+ el.className = 'card';
27
+ if (href) { el.href = href; el.target = '_blank'; el.rel = 'noopener noreferrer'; }
28
+
29
+ if (p.art_url && safeHref(p.art_url)) {
30
+ var img = document.createElement('img');
31
+ img.className = 'art';
32
+ img.src = p.art_url;
33
+ img.alt = '';
34
+ img.loading = 'lazy';
35
+ el.appendChild(img);
36
+ }
37
+ var h = document.createElement('h2');
38
+ h.textContent = p.name || p.origin || 'Untitled project';
39
+ el.appendChild(h);
40
+ if (p.tagline) {
41
+ var tag = document.createElement('p');
42
+ tag.textContent = p.tagline;
43
+ el.appendChild(tag);
44
+ }
45
+ if (p.origin) {
46
+ var o = document.createElement('span');
47
+ o.className = 'origin';
48
+ o.textContent = String(p.origin).replace(/^https?:\/\//, '');
49
+ el.appendChild(o);
50
+ }
51
+ return el;
52
+ }
53
+
54
+ fetch('/api/gds/projects/featured', { headers: { Accept: 'application/json' } })
55
+ .then(function (r) { if (!r.ok) throw new Error('http ' + r.status); return r.json(); })
56
+ .then(function (data) {
57
+ var projects = (data && Array.isArray(data.projects)) ? data.projects : [];
58
+ if (projects.length === 0) { show(empty); return; }
59
+ projects.forEach(function (p) { grid.appendChild(card(p)); });
60
+ show(grid);
61
+ })
62
+ .catch(function () {
63
+ // A missing endpoint (non-hub instance) is not an error to the visitor — show
64
+ // the empty state rather than a scary failure.
65
+ show(empty);
66
+ });
67
+ }());
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.17.3",
3
+ "version": "1.18.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.17.3",
9
+ "version": "1.18.3",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.17.3",
3
+ "version": "1.18.3",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -29,6 +29,13 @@
29
29
  --font-mono:"Fira Code",ui-monospace,SFMono-Regular,Menlo,monospace;
30
30
  }
31
31
  *{box-sizing:border-box;margin:0;padding:0;}
32
+ /* The `hidden` attribute must always win. Several blocks below set an explicit
33
+ `display` via a class (.field→block, .row2→grid); an author rule like that
34
+ silently defeats the user-agent [hidden] rule, so `el.hidden = true` fails to
35
+ hide them. The Home step (task 1002228) toggles el.hidden to switch between the
36
+ adopt-picker / fresh-name / manual sub-panels — without this they ALL render at
37
+ once. Make hidden authoritative for the whole page. */
38
+ [hidden]{display:none !important;}
32
39
  html{scroll-behavior:smooth;}
33
40
  body{
34
41
  font-family:var(--font-body); color:var(--ink);
@@ -182,6 +189,25 @@ input[type=text].bad{border-color:var(--err);}
182
189
  }
183
190
  .reassure.on{display:block;}
184
191
 
192
+ /* one-click deep link to GitHub's own "New repository" page with the name
193
+ pre-filled (greenfield only). No permissions — the visitor creates the empty
194
+ repo in GitHub's own UI; we just fill in the blanks. */
195
+ .ghbtn{
196
+ display:inline-flex;align-items:center;gap:9px;text-decoration:none;cursor:pointer;
197
+ background:#24292f;color:#fff;border:none;border-radius:999px;
198
+ font-family:var(--font-body);font-weight:700;font-size:15px;padding:12px 22px;
199
+ box-shadow:0 12px 26px -12px rgba(36,41,47,0.55);
200
+ transition:transform .12s ease, background .12s ease;
201
+ }
202
+ .ghbtn:hover{background:#1b1f24;transform:translateY(-1px);}
203
+ .ghbtn:active{transform:translateY(0) scale(.98);}
204
+ .ghbtn .gh{width:18px;height:18px;fill:currentColor;}
205
+ .ghcreate{display:none;margin-bottom:18px;}
206
+ .ghcreate.on{display:block;}
207
+ .ghcreate-note{font-size:13px;font-weight:500;color:var(--ink-faint);line-height:1.5;margin-top:9px;max-width:52ch;}
208
+ .ghcreate-note strong{color:var(--ink-soft);}
209
+ .steps .ghbtn{font-size:14px;padding:9px 16px;margin-top:10px;}
210
+
185
211
  /* buttons */
186
212
  .nav{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:26px;}
187
213
  .btn{
@@ -308,6 +334,16 @@ input[type=text].bad{border-color:var(--err);}
308
334
  @media (prefers-reduced-motion:reduce){
309
335
  *,*::before,*::after{animation:none !important;transition:none !important;}
310
336
  }
337
+ /* GitHub repo picker (task 1002099) */
338
+ .linklike{background:none;border:0;padding:0;margin-left:12px;color:var(--accent-deep);font:inherit;font-size:13px;cursor:pointer;text-decoration:underline;}
339
+ #ghConnect .btn{margin-top:4px;}
340
+ .ghlist{margin-top:8px;max-height:220px;overflow-y:auto;border:1px solid var(--line,#e7d8c4);border-radius:10px;background:var(--bg-deep,#fff);}
341
+ .ghlist:empty{display:none;}
342
+ .ghrow{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:none;border:0;border-bottom:1px solid rgba(42,31,23,0.06);padding:9px 12px;font:inherit;font-size:13.5px;color:var(--ink,#2a1f17);cursor:pointer;}
343
+ .ghrow:last-child{border-bottom:0;}
344
+ .ghrow:hover{background:#fff9f2;}
345
+ .ghrow[aria-selected=true]{background:#fff3e6;box-shadow:inset 3px 0 0 var(--accent,#f97316);font-weight:600;}
346
+ .ghrow .priv{margin-left:auto;font-size:11px;font-weight:600;color:var(--ink-faint,#9b8a78);background:rgba(42,31,23,0.06);border-radius:5px;padding:1px 6px;}
311
347
  </style>
312
348
  </head>
313
349
  <body>
@@ -386,7 +422,31 @@ input[type=text].bad{border-color:var(--err);}
386
422
  </button>
387
423
  </div>
388
424
  <div class="reassure" id="adoptNote">Adopting never overwrites your files: you get a conflict pre-flight first, nothing is written until you accept, and your existing backlog (issues, TODOs) is imported as real tasks.</div>
389
- <div class="row2">
425
+
426
+ <!-- GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099.
427
+ Progressive enhancement: when the signed-in user grants repo access we let
428
+ them PICK a repo (adopt) or NAME one we auto-create (fresh). Without that
429
+ access — not granted yet, or the server can't store the token — we fall back
430
+ to the manual owner/repo inputs below (#manualHome), which stay authoritative. -->
431
+ <div id="ghConnect" class="reassure">
432
+ <p id="ghConnectMsg">&nbsp;</p>
433
+ <button type="button" class="btn" id="ghConnectBtn">Connect GitHub</button>
434
+ <button type="button" class="linklike" id="ghManualToggle">or enter it manually</button>
435
+ </div>
436
+ <div id="ghBusy" class="hint" hidden>Reading your GitHub repositories…</div>
437
+ <div id="ghAdopt" class="field" hidden>
438
+ <span>Pick one of your repositories</span>
439
+ <input type="text" id="ghSearch" placeholder="Search your repos…" autocomplete="off">
440
+ <div id="ghList" class="ghlist" role="listbox" aria-label="Your repositories"></div>
441
+ <div class="hint" id="ghAdoptHint">&nbsp;</div>
442
+ </div>
443
+ <div id="ghFresh" class="field" hidden>
444
+ <span>New repository name</span>
445
+ <input type="text" id="ghNewName" placeholder="mercury" autocomplete="off" maxlength="100">
446
+ <div class="hint" id="ghFreshHint">We’ll create an empty repo at <code id="ghFreshPreview" style="font-family:var(--font-mono)">github.com/you/mercury</code>.</div>
447
+ </div>
448
+
449
+ <div class="row2" id="manualHome">
390
450
  <label class="field"><span>GitHub owner <small>(user or org)</small></span>
391
451
  <input type="text" id="f-owner" placeholder="acme" autocomplete="off" maxlength="60">
392
452
  </label>
@@ -394,6 +454,13 @@ input[type=text].bad{border-color:var(--err);}
394
454
  <input type="text" id="f-repo" placeholder="mercury" autocomplete="off" maxlength="120">
395
455
  </label>
396
456
  </div>
457
+ <div class="ghcreate" id="ghCreate">
458
+ <a class="ghbtn" id="ghCreateBtn" href="https://github.com/new" target="_blank" rel="noopener">
459
+ <svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
460
+ Create this repo on GitHub
461
+ </a>
462
+ <p class="ghcreate-note">Opens GitHub with the name already filled in — just click <strong>Create repository</strong> and leave it empty, then come back and press Next. Already made it? Just press Next.</p>
463
+ </div>
397
464
  <div class="hint" id="h-home">&nbsp;</div>
398
465
  <div class="nav">
399
466
  <button type="button" class="btn ghost" data-back="1">← Back</button>
@@ -470,7 +537,7 @@ input[type=text].bad{border-color:var(--err);}
470
537
  /* ── state ── */
471
538
  var DRAFT_KEY = 'cb-create-draft';
472
539
  var CREATED_KEY = 'cb-created-instance';
473
- var state = { step: 0, name: '', mode: 'greenfield', owner: '', repo: '', slug: '', slugTouched: false, domain: '' };
540
+ var state = { step: 0, name: '', mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '' };
474
541
  try { Object.assign(state, JSON.parse(sessionStorage.getItem(DRAFT_KEY) || '{}')); } catch (e) {}
475
542
  var me = null;
476
543
 
@@ -483,6 +550,7 @@ input[type=text].bad{border-color:var(--err);}
483
550
  if (handedName && handedName.trim()) {
484
551
  state.name = handedName.trim().slice(0, 80);
485
552
  state.slug = deriveSlug(state.name); state.slugTouched = false; // (hoisted declaration)
553
+ state.repo = deriveRepo(state.name); state.repoTouched = false; // auto-repo from the name
486
554
  state.step = state.step >= 1 && state.step <= 4 ? state.step : 1;
487
555
  try { sessionStorage.setItem(DRAFT_KEY, JSON.stringify(state)); } catch (e) {}
488
556
  history.replaceState(null, '', location.pathname);
@@ -507,14 +575,19 @@ input[type=text].bad{border-color:var(--err);}
507
575
  var v = String(s || '');
508
576
  return v.length >= 2 && v.length <= 40 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(v) && v.indexOf('--') === -1;
509
577
  }
510
- function deriveSlug(name) {
578
+ function slugify(name, maxLen) {
511
579
  return String(name || '').toLowerCase()
512
580
  .replace(/['’]/g, '')
513
581
  .replace(/[^a-z0-9]+/g, '-')
514
582
  .replace(/-+/g, '-')
515
583
  .replace(/^-|-$/g, '')
516
- .slice(0, 40);
584
+ .slice(0, maxLen);
517
585
  }
586
+ function deriveSlug(name) { return slugify(name, 40); }
587
+ /* Repo-name default from the project name — the same slug-safe transform (GitHub
588
+ repo names accept [A-Za-z0-9._-], so a lowercase-dashed name is always valid and
589
+ matches the placeholder). Wider cap than the handle; the visitor can still edit. */
590
+ function deriveRepo(name) { return slugify(name, 100); }
518
591
  function looksLikeDomain(d) {
519
592
  return /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/i.test(String(d || ''));
520
593
  }
@@ -524,6 +597,20 @@ input[type=text].bad{border-color:var(--err);}
524
597
  var origin = 'https://' + domain;
525
598
  return { homepageUrl: origin, callbackUrl: origin + '/api/gds/auth/web/callback' };
526
599
  }
600
+ /* GitHub's own "New repository" page, pre-filled with the owner + name. No auth or
601
+ permissions on our side — the visitor creates the empty repo in GitHub's UI, which
602
+ is exactly what the scaffold then clones. `owner` selects the account/org; omitting
603
+ a blank param just leaves that field empty on GitHub. */
604
+ function githubNewRepoUrl(owner, repo) {
605
+ var u = new URL('https://github.com/new');
606
+ if (repo) u.searchParams.set('name', repo);
607
+ if (owner) u.searchParams.set('owner', owner);
608
+ return u.toString();
609
+ }
610
+ function updateGhCreateHref() {
611
+ var a = $('ghCreateBtn');
612
+ if (a) a.href = githubNewRepoUrl(state.owner, state.repo);
613
+ }
527
614
  function esc(s) {
528
615
  return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
529
616
  return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
@@ -549,6 +636,7 @@ input[type=text].bad{border-color:var(--err);}
549
636
  saveDraft();
550
637
  var h = $(panels[step]).querySelector('h1');
551
638
  if (h) { h.setAttribute('tabindex', '-1'); h.focus({ preventScroll: false }); }
639
+ if (step === 2) { syncHome(); ensureRepoAccess(); }
552
640
  if (step === 4) renderReview();
553
641
  }
554
642
 
@@ -559,12 +647,25 @@ input[type=text].bad{border-color:var(--err);}
559
647
  $('f-slug').value = state.slug;
560
648
  $('f-domain').value = state.domain;
561
649
 
650
+ /* Auto-fill the repo name from the project name until the visitor edits it
651
+ directly (mirrors the handle's auto-derive). The owner is filled once we know
652
+ who's signed in — see the /me boot below. */
653
+ if (!state.repoTouched && state.name && !state.repo) {
654
+ state.repo = deriveRepo(state.name);
655
+ $('f-repo').value = state.repo;
656
+ }
657
+
562
658
  $('f-name').addEventListener('input', function () {
563
659
  state.name = this.value;
564
660
  if (!state.slugTouched) {
565
661
  state.slug = deriveSlug(state.name);
566
662
  $('f-slug').value = state.slug;
567
663
  }
664
+ if (!state.repoTouched) {
665
+ state.repo = deriveRepo(state.name);
666
+ $('f-repo').value = state.repo;
667
+ updateGhCreateHref();
668
+ }
568
669
  var h = $('h-name');
569
670
  if (state.slug) { h.innerHTML = 'its handle on the platform: <code>' + esc(state.slug) + '</code>'; h.className = 'hint'; }
570
671
  else { h.innerHTML = '&nbsp;'; }
@@ -577,14 +678,115 @@ input[type=text].bad{border-color:var(--err);}
577
678
  $('c-green').setAttribute('aria-pressed', String(mode === 'greenfield'));
578
679
  $('c-adopt').setAttribute('aria-pressed', String(mode === 'adopt'));
579
680
  $('adoptNote').classList.toggle('on', mode === 'adopt');
681
+ // The "create the repo on GitHub" button only makes sense when starting fresh —
682
+ // an adopted repo already exists.
683
+ $('ghCreate').classList.toggle('on', mode === 'greenfield');
684
+ updateGhCreateHref();
580
685
  saveDraft();
686
+ syncHome();
581
687
  }
582
688
  $('c-green').addEventListener('click', function () { setMode('greenfield'); });
583
689
  $('c-adopt').addEventListener('click', function () { setMode('adopt'); });
584
690
  setMode(state.mode);
585
691
 
586
- $('f-owner').addEventListener('input', function () { state.owner = this.value.trim(); saveDraft(); });
587
- $('f-repo').addEventListener('input', function () { state.repo = this.value.trim(); saveDraft(); });
692
+ /* ── GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099 / ADR 0143.
693
+ Progressive enhancement: probe GET /github/repos once we reach step 2.
694
+ 200 → we hold repo access → show the picker (adopt) / name field (fresh);
695
+ 403 → offer "Connect GitHub" (a ?repos=1 re-sign-in);
696
+ 503 (server can't store the token) or any error → the manual inputs.
697
+ #manualHome (the classic owner/repo fields) is the always-safe fallback. ── */
698
+ var repoAccess = null; // null=unknown/loading · true · 'need-connect' · 'unavailable' · 'manual'
699
+ var repoList = []; // the user's repos (from the probe) — feeds the picker + the fresh-name clash hint
700
+ var ghProbed = false;
701
+
702
+ function isManualHome() { return repoAccess === 'manual' || repoAccess === 'unavailable'; }
703
+ function validRepoName(s) { return /^[A-Za-z0-9._-]{1,100}$/.test(String(s || '')); }
704
+ function myLogin() { return (me && me.github_login) || 'you'; }
705
+
706
+ function connectRepos(e) {
707
+ if (e) e.preventDefault();
708
+ // Same-tab navigation so the sessionStorage draft round-trips back to step 2.
709
+ window.location.assign(API + '/auth/web/start?return=/create&repos=1');
710
+ }
711
+ $('ghConnectBtn').addEventListener('click', connectRepos);
712
+ $('ghManualToggle').addEventListener('click', function () { repoAccess = 'manual'; syncHome(); $('f-owner').focus(); });
713
+
714
+ function syncHome() {
715
+ var need = repoAccess === 'need-connect';
716
+ var ok = repoAccess === true;
717
+ var adopt = state.mode === 'adopt';
718
+ $('ghBusy').hidden = !!repoAccess; // busy only while still probing (null)
719
+ $('ghConnect').classList.toggle('on', need); // .reassure box: shown via .on (not hidden — .reassure sets display:none)
720
+ $('ghAdopt').hidden = !(ok && adopt);
721
+ $('ghFresh').hidden = !(ok && !adopt);
722
+ $('manualHome').hidden = !isManualHome();
723
+ if (need) {
724
+ $('ghConnectMsg').textContent = adopt
725
+ ? 'Connect your GitHub account and pick the repository to adopt.'
726
+ : 'Connect your GitHub account and we’ll create the empty repo for you.';
727
+ }
728
+ if (ok && adopt) renderRepoList();
729
+ if (ok && !adopt) syncFresh();
730
+ }
731
+
732
+ function syncFresh() {
733
+ // Fresh + access: the repo is created under the signed-in account. Seed owner for
734
+ // the review/target now; the create response confirms the real full_name.
735
+ state.owner = myLogin();
736
+ $('ghNewName').value = state.repo || '';
737
+ updateFreshPreview();
738
+ }
739
+ function updateFreshPreview() {
740
+ var hint = $('ghFreshHint');
741
+ var clash = state.repo && repoList.some(function (r) { return String(r.name).toLowerCase() === state.repo.toLowerCase(); });
742
+ if (state.repo && !validRepoName(state.repo)) {
743
+ hint.className = 'hint err'; hint.textContent = 'Use letters, numbers, and . _ - (1–100 chars).';
744
+ } else if (clash) {
745
+ hint.className = 'hint err'; hint.innerHTML = 'You already have a repo named <strong>' + esc(state.repo) + '</strong> — pick another name, or adopt it instead.';
746
+ } else {
747
+ hint.className = 'hint'; hint.innerHTML = 'We’ll create an empty repo at <code style="font-family:var(--font-mono)">github.com/' + esc(myLogin()) + '/' + esc(state.repo || 'name') + '</code>.';
748
+ }
749
+ }
750
+ $('ghNewName').addEventListener('input', function () { state.repo = this.value.trim(); updateFreshPreview(); saveDraft(); });
751
+
752
+ function renderRepoList() {
753
+ var q = ($('ghSearch').value || '').trim().toLowerCase();
754
+ var rows = repoList.filter(function (r) { return !q || String(r.full_name).toLowerCase().indexOf(q) >= 0; }).slice(0, 100);
755
+ var sel = state.owner && state.repo ? (state.owner + '/' + state.repo) : '';
756
+ $('ghList').innerHTML = rows.length
757
+ ? rows.map(function (r) {
758
+ return '<button type="button" class="ghrow" role="option" data-full="' + esc(r.full_name) + '" aria-selected="' + (r.full_name === sel) + '">' +
759
+ '<span>' + esc(r.full_name) + '</span>' + (r.private ? '<span class="priv">Private</span>' : '') + '</button>';
760
+ }).join('')
761
+ : '<div class="hint" style="padding:10px 12px">No repositories match “' + esc(q) + '”.</div>';
762
+ }
763
+ $('ghSearch').addEventListener('input', renderRepoList);
764
+ $('ghList').addEventListener('click', function (ev) {
765
+ var row = ev.target.closest('.ghrow'); if (!row) return;
766
+ var full = row.getAttribute('data-full'); var slash = full.indexOf('/');
767
+ state.owner = full.slice(0, slash); state.repo = full.slice(slash + 1);
768
+ saveDraft();
769
+ Array.prototype.forEach.call($('ghList').querySelectorAll('.ghrow'), function (b) { b.setAttribute('aria-selected', String(b === row)); });
770
+ var h = $('ghAdoptHint'); h.className = 'hint ok'; h.innerHTML = 'Adopting <strong>' + esc(full) + '</strong>.';
771
+ });
772
+
773
+ function ensureRepoAccess() {
774
+ if (ghProbed && repoAccess !== null) return; // already known this session
775
+ if (isManualHome()) return; // user chose manual — don't nag
776
+ repoAccess = null; syncHome(); // show "reading your repos…"
777
+ fetch(API + '/github/repos', { credentials: 'same-origin' })
778
+ .then(function (res) {
779
+ ghProbed = true;
780
+ if (res.status === 200) return res.json().then(function (d) { repoList = (d && d.repos) || []; repoAccess = true; });
781
+ if (res.status === 403 || res.status === 401) { repoAccess = 'need-connect'; return; }
782
+ repoAccess = 'unavailable'; return; // 503 or anything else → manual fallback
783
+ })
784
+ .catch(function () { ghProbed = true; repoAccess = 'unavailable'; })
785
+ .then(syncHome);
786
+ }
787
+
788
+ $('f-owner').addEventListener('input', function () { state.owner = this.value.trim(); state.ownerTouched = true; updateGhCreateHref(); saveDraft(); });
789
+ $('f-repo').addEventListener('input', function () { state.repo = this.value.trim(); state.repoTouched = true; updateGhCreateHref(); saveDraft(); });
588
790
 
589
791
  $('f-slug').addEventListener('input', function () {
590
792
  state.slugTouched = true;
@@ -612,7 +814,17 @@ input[type=text].bad{border-color:var(--err);}
612
814
  return true;
613
815
  }
614
816
  if (step === 2) {
615
- if (!state.owner || !state.repo) { $('h-home').textContent = 'Both owner and repository name are needed.'; $('h-home').className = 'hint err'; return false; }
817
+ var homeErr = function (msg) { $('h-home').textContent = msg; $('h-home').className = 'hint err'; return false; };
818
+ if (repoAccess === 'need-connect') return homeErr('Connect GitHub to pick or create your repository — or choose “enter it manually”.');
819
+ if (repoAccess === true && state.mode !== 'adopt') {
820
+ // Fresh with access: just the new repo NAME (owner is the signed-in account).
821
+ if (!validRepoName(state.repo)) return homeErr('Name the new repository (letters, numbers, and . _ -).');
822
+ } else {
823
+ // Adopt (picker or manual) and the manual fallback: need a concrete owner + repo.
824
+ if (!state.owner || !state.repo) {
825
+ return homeErr(state.mode === 'adopt' && repoAccess === true ? 'Pick one of your repositories.' : 'Both owner and repository name are needed.');
826
+ }
827
+ }
616
828
  $('h-home').innerHTML = '&nbsp;'; $('h-home').className = 'hint';
617
829
  return true;
618
830
  }
@@ -662,20 +874,58 @@ input[type=text].bad{border-color:var(--err);}
662
874
  return 'Couldn’t file the request: ' + (err && err.message ? err.message : 'something went sideways. Try again.');
663
875
  }
664
876
 
877
+ /* Fresh + repo access: auto-create the empty GitHub repo NOW (the commit moment),
878
+ then hand its real full_name to provisioning. No-op for adopt / manual / no-access.
879
+ Idempotent across a retry — once created we reuse the stashed full_name so a second
880
+ click doesn't trip GitHub's 422 "name already exists". Errors set err.gotoHome so
881
+ the caller bounces the user back to the Home step to fix the name / reconnect. */
882
+ function ensureFreshRepo() {
883
+ if (state.mode === 'adopt' || repoAccess !== true) return Promise.resolve();
884
+ if (state.repoAutoCreated && state.owner && state.repo) return Promise.resolve();
885
+ return fetch(API + '/github/repos', {
886
+ method: 'POST', credentials: 'same-origin',
887
+ headers: { 'Content-Type': 'application/json' },
888
+ body: JSON.stringify({ name: state.repo })
889
+ }).then(function (res) {
890
+ return res.json().catch(function () { return {}; }).then(function (data) {
891
+ if (res.status === 401 || res.status === 403) {
892
+ repoAccess = 'need-connect';
893
+ var e0 = new Error('reconnect'); e0.gotoHome = true;
894
+ e0.homeMsg = 'Your GitHub authorization expired — reconnect on the Home step to create the repo.';
895
+ throw e0;
896
+ }
897
+ if (!res.ok) {
898
+ var e = new Error((data.error && (data.error.message || data.error.code)) || ('HTTP ' + res.status));
899
+ e.data = data;
900
+ if (data.error && data.error.code === 'name_taken') { e.gotoHome = true; e.homeMsg = data.error.message; }
901
+ throw e;
902
+ }
903
+ var full = data.repo && data.repo.full_name;
904
+ if (full && full.indexOf('/') > 0) {
905
+ state.owner = full.slice(0, full.indexOf('/'));
906
+ state.repo = full.slice(full.indexOf('/') + 1);
907
+ }
908
+ state.repoAutoCreated = true; saveDraft();
909
+ });
910
+ });
911
+ }
912
+
665
913
  $('doCreate').addEventListener('click', function () {
666
914
  var btn = this;
667
915
  var alertBox = $('createAlert');
668
916
  alertBox.classList.remove('on');
669
917
  btn.disabled = true; btn.textContent = 'Raising it into the sky…';
670
- fetch(API + '/provisioning/instances', {
671
- method: 'POST', credentials: 'same-origin',
672
- headers: { 'Content-Type': 'application/json' },
673
- body: JSON.stringify({
674
- slug: state.slug,
675
- hosting_shape: 'standalone',
676
- domain: state.domain || undefined,
677
- target_ref: state.owner + '/' + state.repo
678
- })
918
+ ensureFreshRepo().then(function () {
919
+ return fetch(API + '/provisioning/instances', {
920
+ method: 'POST', credentials: 'same-origin',
921
+ headers: { 'Content-Type': 'application/json' },
922
+ body: JSON.stringify({
923
+ slug: state.slug,
924
+ hosting_shape: 'standalone',
925
+ domain: state.domain || undefined,
926
+ target_ref: state.owner + '/' + state.repo
927
+ })
928
+ });
679
929
  }).then(function (res) {
680
930
  if (res.status === 401) { window.location.assign(API + '/auth/web/start?return=/create'); throw new Error('signing in'); }
681
931
  return res.json().catch(function () { return {}; }).then(function (data) {
@@ -698,9 +948,17 @@ input[type=text].bad{border-color:var(--err);}
698
948
  startProgress(data.instance && data.instance.id);
699
949
  }).catch(function (err) {
700
950
  if (String(err && err.message) === 'signing in') return;
951
+ btn.disabled = false; btn.textContent = 'Create my project';
952
+ if (err && err.gotoHome) {
953
+ // A repo-creation problem (name taken / reconnect) — bounce back to the Home
954
+ // step with the reason, so the user fixes it where the repo is chosen.
955
+ show(2);
956
+ $('h-home').textContent = err.homeMsg || 'Please revisit the repository step.';
957
+ $('h-home').className = 'hint err';
958
+ return;
959
+ }
701
960
  alertBox.textContent = friendlyCreateError(err);
702
961
  alertBox.classList.add('on');
703
- btn.disabled = false; btn.textContent = 'Create my project';
704
962
  });
705
963
  });
706
964
 
@@ -719,9 +977,14 @@ input[type=text].bad{border-color:var(--err);}
719
977
 
720
978
  var steps = [];
721
979
  var repoLabel = state.owner && state.repo ? '<strong>' + esc(state.owner + '/' + state.repo) + '</strong>' : 'your new repository';
980
+ var ghNewUrl = githubNewRepoUrl(state.owner, state.repo);
981
+ var ghIcon = '<svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>';
722
982
  steps.push(adopt
723
983
  ? '<li data-onboard-step="github-repo"><b>Layer onto your existing repo</b><p>In your repo run <code style="font-family:var(--font-mono)">bongos init --adopt</code>. It shows a conflict pre-flight and writes <em>nothing</em> until you accept — your files and history stay yours, and your existing backlog is imported. Commit and push.</p></li>'
724
- : '<li data-onboard-step="github-repo"><b>Create the empty GitHub repo</b><p>On github.com: New repository → ' + repoLabel + ' → leave it completely empty.</p></li>');
984
+ : (state.repoAutoCreated
985
+ ? '<li data-onboard-step="github-repo"><b>Your empty repo is ready</b><p>We created ' + repoLabel + ' on GitHub for you — empty and waiting. The scaffold lands there when the build runs; nothing for you to do here.</p></li>'
986
+ : '<li data-onboard-step="github-repo"><b>Create the empty GitHub repo</b><p>Do this first — the build needs the repo to exist. Click below (GitHub opens with the name already filled in), press <strong>Create repository</strong>, and leave it completely empty. Seeing “repository not found” above? That just means the repo isn’t created yet — make it here.</p>' +
987
+ '<a class="ghbtn" href="' + esc(ghNewUrl) + '" target="_blank" rel="noopener">' + ghIcon + 'Create ' + repoLabel + ' on GitHub</a></li>'));
725
988
  if (!state.domain) {
726
989
  steps.push('<li><b>Add a domain you own</b><p>Sign-in on your new instance needs a real domain. When you have one, come back here or ask your hall — the address gets wired in minutes.</p></li>');
727
990
  }
@@ -829,8 +1092,10 @@ input[type=text].bad{border-color:var(--err);}
829
1092
  again.addEventListener('click', function () {
830
1093
  stopPolling();
831
1094
  try { sessionStorage.removeItem(CREATED_KEY); sessionStorage.removeItem(DRAFT_KEY); } catch (e) {}
832
- state = { step: 1, name: '', mode: 'greenfield', owner: '', repo: '', slug: '', slugTouched: false, domain: '' };
1095
+ state = { step: 1, name: '', mode: 'greenfield', owner: '', ownerTouched: false, repo: '', repoTouched: false, slug: '', slugTouched: false, domain: '' };
833
1096
  $('f-name').value = ''; $('f-owner').value = ''; $('f-repo').value = ''; $('f-slug').value = ''; $('f-domain').value = '';
1097
+ // Re-seed the owner from the signed-in account for the next project.
1098
+ if (me && me.github_login) { state.owner = me.github_login; $('f-owner').value = state.owner; }
834
1099
  $('h-name').innerHTML = '&nbsp;';
835
1100
  setMode('greenfield');
836
1101
  show(1);
@@ -884,6 +1149,14 @@ input[type=text].bad{border-color:var(--err);}
884
1149
  .then(function (data) {
885
1150
  me = data.builder || data.me || data;
886
1151
  renderWho();
1152
+ // Default the GitHub owner to the signed-in account until the visitor edits
1153
+ // it — one less field to fill for the common "my own account" case.
1154
+ if (!state.ownerTouched && !state.owner && me && me.github_login) {
1155
+ state.owner = me.github_login;
1156
+ $('f-owner').value = state.owner;
1157
+ updateGhCreateHref();
1158
+ saveDraft();
1159
+ }
887
1160
  // returning after a create? resume watching that instance instead of a blank wizard
888
1161
  var created = null;
889
1162
  try { created = JSON.parse(sessionStorage.getItem(CREATED_KEY) || 'null'); } catch (e) {}