Skip to content

Inline submit — no page navigation, everything in background#5

Merged
jonathanpopham merged 3 commits intomainfrom
feat/inline-submit
Mar 2, 2026
Merged

Inline submit — no page navigation, everything in background#5
jonathanpopham merged 3 commits intomainfrom
feat/inline-submit

Conversation

@jonathanpopham
Copy link
Contributor

@jonathanpopham jonathanpopham commented Mar 2, 2026

Problem

The previous flow redirected users to a GitHub issue form on a different page. Terrible UX — takes them off the site, requires a GitHub account, multiple clicks.

Solution

Everything happens inline now. The user never leaves the page.

User flow

  1. Paste a GitHub URL in the submit field
  2. See a preview: repos.supermodeltools.com/{name}/
  3. Click Request
  4. Button shows "Submitting..." with loading state
  5. Success message appears inline: "Submitted! Docs will be generated at [link]"
  6. Behind the scenes: Cloudflare Worker creates a GitHub issue, auto-add-repo.yml handles the rest

Error handling

  • Invalid URL: inline error
  • API failure: inline "try again" message
  • Network error: inline "try again" message

Architecture

Homepage POST /api/request -> Cloudflare Worker -> GitHub Issues API -> auto-add-repo.yml -> fork, arch-docs, repos.yaml

Changes

File What
generate-index.go Rewrite JS to use fetch() with loading/success/error states. Remove nav redirect button.
worker/index.js Cloudflare Worker: validates URL, creates GitHub issue, returns JSON
worker/wrangler.toml Worker config with route for repos.supermodeltools.com/api/*

Setup

  1. cd worker && npx wrangler secret put GITHUB_TOKEN (fine-grained PAT with issues:write)
  2. npx wrangler deploy
  3. Add route in Cloudflare: repos.supermodeltools.com/api/* to repo-request worker

Test plan

  • Deploy worker to Cloudflare
  • Paste valid GitHub URL, see preview, click Request, see success inline
  • Paste invalid URL, button stays disabled
  • Test error states (bad network, API failure)
  • Verify GitHub issue created with repo-request label
  • Verify auto-add-repo.yml triggers from the created issue

Summary by CodeRabbit

  • New Features

    • Replaced direct GitHub submission with an API-driven request flow; submit button now reads “Generate.”
    • Adds a public request API and a user-facing loading page that redirects to generated docs.
    • Produces a site 404 page as part of the build output.
  • Improvements

    • New feedback system with loading, success, and error states; improved error messaging.
    • Refined keyboard interactions, focus behavior, and no-results handling.
  • Other

    • Removed the old header request link/CTA.

The previous flow took users off the page to a GitHub issue form.
Now everything happens in the background:

1. User pastes URL, clicks Request
2. Frontend POSTs to /api/request (Cloudflare Worker)
3. Worker creates the GitHub issue (triggers auto-add pipeline)
4. User sees inline success message with their docs link

Changes:
- Add worker/ with Cloudflare Worker that proxies to GitHub Issues API
- Rewrite frontend JS to use fetch() with loading/success/error states
- Remove nav "Request a Repo" button (the inline form is the entry point)
- Remove .request-btn CSS, add .submit-feedback state classes

Worker deployment:
  cd worker && npx wrangler secret put GITHUB_TOKEN
  npx wrangler deploy

Route repos.supermodeltools.com/api/* to the worker in Cloudflare.
@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Walkthrough

Frontend request flow moved from a template/issue-link to an API-backed flow: the site POSTs repo URLs to a new Cloudflare Worker at /api/request which validates the repo, creates a GitHub issue, returns JSON (including a generating URL), and a new generate404() path produces site/404.html.

Changes

Cohort / File(s) Summary
Frontend: index & templates
generate-index.go
Replaced submit-preview with submit-feedback; added showFeedback() helper, API_URL/api/request; changed button label to “Generate”; async POST submission flow with loading states, enter-key behavior, no-results handling; added generate404() and notFoundTemplate.
Cloudflare Worker
worker/index.js
New Worker export with fetch(request, env) handling: CORS preflight, POST /api/request validating/normalizing GitHub repo URLs, creates GitHub issue using env.GITHUB_TOKEN, returns JSON {success,name,upstream,generating_url,docs_url}; serves /generating/{name} skeleton page; includes helpers and HTML skeleton.
Worker config
worker/wrangler.toml
New Wrangler config for repo-request (entry index.js, compatibility_date = "2024-01-01"), with commented route example for proxying /api/*.

Sequence Diagram

sequenceDiagram
    participant User as User (Browser)
    participant Frontend as Frontend (site)
    participant Worker as Cloudflare Worker
    participant GitHub as GitHub API

    User->>Frontend: Enter repo URL & click Generate
    Frontend->>Frontend: validate input, show loading state
    Frontend->>Worker: POST /api/request { "url": "https://github.com/owner/repo" }
    Worker->>Worker: parse JSON, normalize & validate repo
    Worker->>GitHub: POST /repos/supermodeltools/supermodeltools.github.io/issues (Auth: Bearer GITHUB_TOKEN)
    alt GitHub success
        GitHub-->>Worker: 201 Created (issue)
        Worker-->>Frontend: 200 { success, name, generating_url, docs_url }
        Frontend-->>User: redirect to generating_url (polls until docs ready)
    else GitHub failure
        GitHub-->>Worker: error
        Worker-->>Frontend: 502 { error }
        Frontend-->>User: show error feedback
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly Related PRs

Poem

A click, a POST, a Worker springs to life—
It names the repo, files the issue, starts the strife.
A generating page hums while docs are spun,
404s get drafted, feedback says "well done". 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: replacing page navigation with an inline submit flow that keeps users on the site with background processing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/inline-submit

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
worker/index.js (2)

76-76: Edge case: JSON parse could fail even on ok response.

If GitHub returns a 200 OK but with malformed JSON (rare, but possible during outages), ghResponse.json() would throw and bubble up as an unhandled error, giving the user a generic 500.

You could wrap it in a try/catch for consistency:

Suggested defensive handling
-    const issue = await ghResponse.json();
+    let issue;
+    try {
+      issue = await ghResponse.json();
+    } catch {
+      console.error('Failed to parse GitHub response');
+      return jsonResponse({ error: 'Failed to submit request. Please try again.' }, 502);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@worker/index.js` at line 76, The call to ghResponse.json() can throw even
when ghResponse.ok is true; wrap the json() call in a try/catch around the
existing const issue = await ghResponse.json() so parsing failures are handled
gracefully, catch the parsing error (e.g., SyntaxError), log or surface a clear
error message including ghResponse.status and the parse error, and rethrow or
return a structured error instead of letting the exception bubble uncaught;
update references to the issue variable usage to account for the failure path.

52-68: Consider validating GITHUB_TOKEN exists before making the API call.

If someone deploys the worker but forgets to set the GITHUB_TOKEN secret, the request will fail with a cryptic 401/403 from GitHub. A quick check upfront would make debugging easier:

+    if (!env.GITHUB_TOKEN) {
+      console.error('GITHUB_TOKEN not configured');
+      return jsonResponse({ error: 'Server misconfiguration' }, 500);
+    }
+
     const ghResponse = await fetch(

This way, if the token is missing, you get a clear log message instead of wondering why GitHub is rejecting requests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@worker/index.js` around lines 52 - 68, Check for presence of env.GITHUB_TOKEN
before calling fetch and bail early with a clear error/log if it's missing:
validate env.GITHUB_TOKEN (used in the fetch headers) immediately before the
fetch that produces ghResponse, and if falsy, call the existing logger or throw
an Error with a descriptive message (e.g., "GITHUB_TOKEN missing: set secret
GITHUB_TOKEN") so the function exits before making the GitHub API call.
generate-index.go (1)

543-546: Small XSS consideration with innerHTML.

You're using innerHTML to set feedback content, which is generally fine when you control all the data. In your case:

  • The preview message uses name from parseRepo(), which is regex-validated to only allow [a-zA-Z0-9._-] — so that's safe.
  • The success message (lines 588-590) includes data.docs_url from the API response.

Since you control the Worker and it constructs docs_url from validated input, this is probably fine. But if you want to be extra safe, you could use textContent for the non-link parts and build the anchor element programmatically:

// Instead of innerHTML with the anchor:
var link = document.createElement('a');
link.href = data.docs_url;
link.textContent = 'repos.supermodeltools.com/' + name + '/';
feedback.textContent = '✓ Submitted! Docs will be generated at ';
feedback.appendChild(link);

Not urgent since you control both sides, just something to be aware of if the architecture ever changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generate-index.go` around lines 543 - 546, The showFeedback function
currently uses feedback.innerHTML to set messages, which risks injecting
data.docs_url; instead, modify showFeedback (and the success message
construction where data.docs_url is used) to set text nodes via
feedback.textContent for plain text and create an anchor element
programmatically (document.createElement('a')) for the docs link, set its href
to data.docs_url and its textContent to the repo path, then append the anchor to
feedback; keep using parseRepo()'s validated name for the path portion and only
use programmatic DOM creation for the link to avoid innerHTML with external
data.
worker/wrangler.toml (1)

1-7: Consider updating compatibility_date to something more current.

The 2024-01-01 date is pretty outdated (we're in March 2026). Cloudflare uses this date to determine which runtime features your worker can access—think of it like telling Cloudflare "use the behavior from January 2024." You might be missing out on newer APIs or security improvements that came after that date.

The good news: old dates never break. Cloudflare keeps supporting them forever. So there's no rush, but if you want access to newer features, you could bump it to something like 2026-03-01 (or closer to today) whenever you get a chance.

One heads up: when you update, test it first. Newer dates sometimes have breaking changes in how certain things behave, so just run your worker through its paces after bumping the date to make sure nothing surprises you.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@worker/wrangler.toml` around lines 1 - 7, Update the compatibility_date value
in wrangler.toml (the compatibility_date symbol) to a more current date (e.g.,
"2026-03-01") to enable newer Cloudflare Worker runtime features; after updating
compatibility_date, run your worker tests and a local/dev deployment to validate
behavior because newer compatibility dates can change runtime semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@worker/index.js`:
- Around line 48-49: The code extracts only the repo segment into name (const
upstream = match[1]; const name = upstream.split('/')[1];) which causes
collisions for identical repo names across different owners; change the naming
to include the owner as well (e.g., split upstream into owner and repo via
upstream.split('/') and set name to a combined, normalized identifier like
`${owner}-${repo}` or otherwise include owner in the docs_url path) so repos are
unique per owner/repo pair and avoid overwrites.

---

Nitpick comments:
In `@generate-index.go`:
- Around line 543-546: The showFeedback function currently uses
feedback.innerHTML to set messages, which risks injecting data.docs_url;
instead, modify showFeedback (and the success message construction where
data.docs_url is used) to set text nodes via feedback.textContent for plain text
and create an anchor element programmatically (document.createElement('a')) for
the docs link, set its href to data.docs_url and its textContent to the repo
path, then append the anchor to feedback; keep using parseRepo()'s validated
name for the path portion and only use programmatic DOM creation for the link to
avoid innerHTML with external data.

In `@worker/index.js`:
- Line 76: The call to ghResponse.json() can throw even when ghResponse.ok is
true; wrap the json() call in a try/catch around the existing const issue =
await ghResponse.json() so parsing failures are handled gracefully, catch the
parsing error (e.g., SyntaxError), log or surface a clear error message
including ghResponse.status and the parse error, and rethrow or return a
structured error instead of letting the exception bubble uncaught; update
references to the issue variable usage to account for the failure path.
- Around line 52-68: Check for presence of env.GITHUB_TOKEN before calling fetch
and bail early with a clear error/log if it's missing: validate env.GITHUB_TOKEN
(used in the fetch headers) immediately before the fetch that produces
ghResponse, and if falsy, call the existing logger or throw an Error with a
descriptive message (e.g., "GITHUB_TOKEN missing: set secret GITHUB_TOKEN") so
the function exits before making the GitHub API call.

In `@worker/wrangler.toml`:
- Around line 1-7: Update the compatibility_date value in wrangler.toml (the
compatibility_date symbol) to a more current date (e.g., "2026-03-01") to enable
newer Cloudflare Worker runtime features; after updating compatibility_date, run
your worker tests and a local/dev deployment to validate behavior because newer
compatibility dates can change runtime semantics.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 676f876 and a8512ea.

📒 Files selected for processing (3)
  • generate-index.go
  • worker/index.js
  • worker/wrangler.toml

worker/index.js Outdated
Comment on lines +48 to +49
const upstream = match[1];
const name = upstream.split('/')[1];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Heads up: potential name collision with repo-only naming.

You're extracting just the repo name (e.g., cool-lib) and ignoring the owner. If two users request different repos with the same name:

  • github.com/alice/cool-lib → name = cool-lib
  • github.com/bob/cool-lib → name = cool-lib

Both would get docs_url: repos.supermodeltools.com/cool-lib/. The second request might overwrite the first or cause confusion.

This might be intentional (maybe you want a "first come, first served" approach), but just wanted to flag it in case it wasn't considered.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@worker/index.js` around lines 48 - 49, The code extracts only the repo
segment into name (const upstream = match[1]; const name =
upstream.split('/')[1];) which causes collisions for identical repo names across
different owners; change the naming to include the owner as well (e.g., split
upstream into owner and repo via upstream.split('/') and set name to a combined,
normalized identifier like `${owner}-${repo}` or otherwise include owner in the
docs_url path) so repos are unique per owner/repo pair and avoid overwrites.

UX flow:
1. User pastes URL, clicks "Generate" (renamed from Request)
2. API fires in background, user is redirected to the docs URL
3. Since docs aren't deployed yet, GitHub Pages serves 404.html
4. 404.html detects the repo path and shows a loading page:
   - Supermodel eye logo with scanning animation
   - Animated progress steps (Forking → Analyzing → Building → Deploying)
   - Polls the URL every 5s for up to 10 minutes
5. When docs deploy, poll detects real content and page auto-reloads
6. User sees the full architecture docs

For genuine 404s (not repo paths), shows a standard 404 page.

Changes:
- generate-index.go: add generate404() + notFoundTemplate
- Rename "Request" → "Generate" in button text
- Frontend JS redirects to docs_url after successful API call
- 404.html: loading animation, step progress, polling logic
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@generate-index.go`:
- Around line 552-555: The showFeedback function currently assigns
feedback.innerHTML = msg which can introduce an XSS sink if msg contains HTML;
change it to render plain text instead (e.g., use feedback.textContent = msg or
create a text node and append it) and keep the className assignment as-is;
update the showFeedback function reference so any callers still pass a plain
string and remove reliance on innerHTML for displaying backend error strings.
- Around line 837-840: The repo-path detection is too broad: change the
isRepoPath logic to only treat single-segment paths as repo names so nested
paths like /foo/bar become 404s; update the check that currently uses
segments.length >= 1 to require segments.length === 1 (and keep the existing
no-dot check on segments[0]) so isRepoPath and repoName (referenced in this
block) correctly identify real repo routes versus multi-segment invalid paths.
- Around line 569-608: submitRequest can be re-entered while a request is in
flight, causing duplicate non-idempotent submissions; add a simple guard (e.g.,
an isSubmitting boolean) near the top of submitRequest and return early if true,
set isSubmitting = true when starting and set it back to false in all exit paths
(success, non-ok/data.error, and catch), and also prevent the Enter-key handler
from invoking submitRequest when isSubmitting is true (or check
submitBtn.classList.contains('loading') there); reference submitRequest,
submitBtn, submitInput, parseRepo, and API_URL when locating where to add the
guard and to ensure the loading state/reset is handled consistently.
- Around line 596-598: The redirect uses data.docs_url directly; validate it
before assigning window.location.href to prevent external redirects. Parse
data.docs_url with the URL constructor (using location.origin as base for
relative paths) and ensure the resulting URL's origin equals location.origin and
that its pathname begins with an expected docs prefix (or is a safe relative
path); only then set window.location.href = data.docs_url, otherwise log/ignore
the value or fallback to a safe internal docs path. Reference data.docs_url and
window.location.href in your changes and apply the check immediately before the
assignment.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8512ea and a4131c3.

📒 Files selected for processing (1)
  • generate-index.go

Comment on lines +552 to +555
function showFeedback(msg, type) {
feedback.className = 'submit-feedback visible ' + type;
feedback.innerHTML = msg;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid innerHTML for feedback messages.

Line [554] uses innerHTML for text/status content. If backend error strings ever contain HTML, this becomes an XSS sink. Use plain text rendering here.

Suggested fix
 function showFeedback(msg, type) {
   feedback.className = 'submit-feedback visible ' + type;
-  feedback.innerHTML = msg;
+  feedback.textContent = msg;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function showFeedback(msg, type) {
feedback.className = 'submit-feedback visible ' + type;
feedback.innerHTML = msg;
}
function showFeedback(msg, type) {
feedback.className = 'submit-feedback visible ' + type;
feedback.textContent = msg;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generate-index.go` around lines 552 - 555, The showFeedback function
currently assigns feedback.innerHTML = msg which can introduce an XSS sink if
msg contains HTML; change it to render plain text instead (e.g., use
feedback.textContent = msg or create a text node and append it) and keep the
className assignment as-is; update the showFeedback function reference so any
callers still pass a plain string and remove reliance on innerHTML for
displaying backend error strings.

Comment on lines +569 to 608
async function submitRequest() {
var parsed = parseRepo(submitInput.value);
if (!parsed) return;

var repoUrl = 'https://github.com/' + parsed;
var name = parsed.split('/')[1];
var url = issueBase
+ '&repo_url=' + encodeURIComponent(repoUrl)
+ '&title=' + encodeURIComponent('[Repo Request] ' + name);
window.open(url, '_blank');

// Loading state
submitBtn.classList.add('loading');
submitBtn.textContent = 'Generating...';
showFeedback('Setting up ' + name + '...', 'preview');

try {
var resp = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: repoUrl }),
});
var data = await resp.json();

if (!resp.ok || !data.success) {
showFeedback(data.error || 'Something went wrong. Please try again.', 'error');
submitBtn.classList.remove('loading');
submitBtn.textContent = 'Generate';
return;
}

// Redirect to the docs page — 404.html shows loading until docs are ready
window.location.href = data.docs_url;
} catch (e) {
showFeedback('Network error. Please try again.', 'error');
submitBtn.classList.remove('loading');
submitBtn.textContent = 'Generate';
}
}

submitBtn.addEventListener('click', submitRequest);
submitInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') submitRequest();
if (e.key === 'Enter' && submitBtn.classList.contains('active')) submitRequest();
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Block re-entrancy to prevent duplicate repo requests.

Right now, Line [607] can still fire submitRequest() while the button is already loading, and submitRequest() itself has no guard. Since this endpoint creates GitHub issues (non-idempotent), duplicate submits are a real risk.

Suggested fix
 async function submitRequest() {
+  if (submitBtn.classList.contains('loading')) return;
   var parsed = parseRepo(submitInput.value);
   if (!parsed) return;
@@
 submitInput.addEventListener('keydown', function(e) {
-  if (e.key === 'Enter' && submitBtn.classList.contains('active')) submitRequest();
+  if (e.key === 'Enter'
+      && submitBtn.classList.contains('active')
+      && !submitBtn.classList.contains('loading')) {
+    submitRequest();
+  }
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function submitRequest() {
var parsed = parseRepo(submitInput.value);
if (!parsed) return;
var repoUrl = 'https://github.com/' + parsed;
var name = parsed.split('/')[1];
var url = issueBase
+ '&repo_url=' + encodeURIComponent(repoUrl)
+ '&title=' + encodeURIComponent('[Repo Request] ' + name);
window.open(url, '_blank');
// Loading state
submitBtn.classList.add('loading');
submitBtn.textContent = 'Generating...';
showFeedback('Setting up ' + name + '...', 'preview');
try {
var resp = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: repoUrl }),
});
var data = await resp.json();
if (!resp.ok || !data.success) {
showFeedback(data.error || 'Something went wrong. Please try again.', 'error');
submitBtn.classList.remove('loading');
submitBtn.textContent = 'Generate';
return;
}
// Redirect to the docs page — 404.html shows loading until docs are ready
window.location.href = data.docs_url;
} catch (e) {
showFeedback('Network error. Please try again.', 'error');
submitBtn.classList.remove('loading');
submitBtn.textContent = 'Generate';
}
}
submitBtn.addEventListener('click', submitRequest);
submitInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') submitRequest();
if (e.key === 'Enter' && submitBtn.classList.contains('active')) submitRequest();
});
async function submitRequest() {
if (submitBtn.classList.contains('loading')) return;
var parsed = parseRepo(submitInput.value);
if (!parsed) return;
var repoUrl = 'https://github.com/' + parsed;
var name = parsed.split('/')[1];
// Loading state
submitBtn.classList.add('loading');
submitBtn.textContent = 'Generating...';
showFeedback('Setting up ' + name + '...', 'preview');
try {
var resp = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: repoUrl }),
});
var data = await resp.json();
if (!resp.ok || !data.success) {
showFeedback(data.error || 'Something went wrong. Please try again.', 'error');
submitBtn.classList.remove('loading');
submitBtn.textContent = 'Generate';
return;
}
// Redirect to the docs page — 404.html shows loading until docs are ready
window.location.href = data.docs_url;
} catch (e) {
showFeedback('Network error. Please try again.', 'error');
submitBtn.classList.remove('loading');
submitBtn.textContent = 'Generate';
}
}
submitBtn.addEventListener('click', submitRequest);
submitInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter'
&& submitBtn.classList.contains('active')
&& !submitBtn.classList.contains('loading')) {
submitRequest();
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generate-index.go` around lines 569 - 608, submitRequest can be re-entered
while a request is in flight, causing duplicate non-idempotent submissions; add
a simple guard (e.g., an isSubmitting boolean) near the top of submitRequest and
return early if true, set isSubmitting = true when starting and set it back to
false in all exit paths (success, non-ok/data.error, and catch), and also
prevent the Enter-key handler from invoking submitRequest when isSubmitting is
true (or check submitBtn.classList.contains('loading') there); reference
submitRequest, submitBtn, submitInput, parseRepo, and API_URL when locating
where to add the guard and to ensure the loading state/reset is handled
consistently.

Comment on lines +596 to +598
// Redirect to the docs page — 404.html shows loading until docs are ready
window.location.href = data.docs_url;
} catch (e) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate docs_url before redirecting.

Line [597] trusts data.docs_url directly. Add same-origin/path validation before navigation so a bad payload can’t trigger an external redirect.

Suggested fix
-        window.location.href = data.docs_url;
+        var next;
+        try {
+          next = new URL(data.docs_url, window.location.origin);
+        } catch (_) {
+          next = null;
+        }
+        if (!next || next.origin !== window.location.origin) {
+          showFeedback('Unexpected response from server. Please try again.', 'error');
+          submitBtn.classList.remove('loading');
+          submitBtn.textContent = 'Generate';
+          return;
+        }
+        window.location.assign(next.pathname + next.search + next.hash);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generate-index.go` around lines 596 - 598, The redirect uses data.docs_url
directly; validate it before assigning window.location.href to prevent external
redirects. Parse data.docs_url with the URL constructor (using location.origin
as base for relative paths) and ensure the resulting URL's origin equals
location.origin and that its pathname begins with an expected docs prefix (or is
a safe relative path); only then set window.location.href = data.docs_url,
otherwise log/ignore the value or fallback to a safe internal docs path.
Reference data.docs_url and window.location.href in your changes and apply the
check immediately before the assignment.

Comment on lines +837 to +840
// If the path looks like a repo name (single segment, no dots suggesting a file),
// show the loading page. Otherwise show genuine 404.
var isRepoPath = segments.length >= 1 && !segments[0].includes('.');
var repoName = segments[0] || '';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Repo-path detection is too broad for 404 routing.

At Line [839], segments.length >= 1 treats nested paths like /foo/bar as “repo generating” instead of a real 404. That gives users the loading UI + polling loop on unrelated invalid paths.

Suggested fix
-    var isRepoPath = segments.length >= 1 && !segments[0].includes('.');
+    var isRepoPath = segments.length === 1 && !segments[0].includes('.');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@generate-index.go` around lines 837 - 840, The repo-path detection is too
broad: change the isRepoPath logic to only treat single-segment paths as repo
names so nested paths like /foo/bar become 404s; update the check that currently
uses segments.length >= 1 to require segments.length === 1 (and keep the
existing no-dot check on segments[0]) so isRepoPath and repoName (referenced in
this block) correctly identify real repo routes versus multi-segment invalid
paths.

After clicking Generate, the user lands on /generating/{name} which
is served by the Cloudflare Worker as a real 200 page. It mirrors
the actual arch-docs layout with shimmer placeholders:

- Same header with repo name, nav links, "All Repos" back link
- Skeleton hero with shimmer stat counters
- Skeleton chart panels (Architecture Overview, Composition)
- Skeleton taxonomy grids (Node Types, Domains, Languages)
- Floating status bar: "Generating docs — analyzing codebase..."

The status bar cycles through stages (forking, analyzing, building
graphs, mapping architecture, deploying) on realistic timers.

Polls the real docs URL every 5s. When docs are ready, the status
bar flashes "Ready!" and redirects to the real page.

The user sees what the page WILL look like, never sees a 404,
and is seamlessly transitioned to the real docs when ready.

Also:
- Renamed "Request" → "Generate" everywhere
- 404.html now just redirects to homepage (for genuine broken links)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
worker/index.js (2)

107-109: escapeHtml is missing single-quote escaping.

Your current function handles & < > " but not '. If name ever appears inside a single-quoted attribute (like onclick='...'), this could be a problem. Currently your template doesn't use single-quoted attributes, so it's probably fine, but for completeness:

 function escapeHtml(s) {
-  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
+  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
 }

This is a nice-to-have since your regex already restricts name to [a-zA-Z0-9._-]+, so no special chars get through anyway.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@worker/index.js` around lines 107 - 109, The escapeHtml function currently
replaces & < > " but omits single quotes; update the escapeHtml function to also
escape single quotes by adding a replacement for "'" (e.g., .replace(/'/g,
'&#39;' ) or &apos;) so inputs are safe when interpolated into single-quoted
attributes; ensure the new .replace call is chained with the existing
replacements in the escapeHtml function.

78-81: Consider not logging the full GitHub API response text.

await ghResponse.text() could contain sensitive info in edge cases. Logging the status code is usually enough for debugging. If you need more detail, maybe log just a truncated snippet.

 if (!ghResponse.ok) {
-  console.error('GitHub API error:', ghResponse.status, await ghResponse.text());
+  console.error('GitHub API error:', ghResponse.status);
   return jsonResponse({ error: 'Failed to submit. Please try again.' }, 502);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@worker/index.js` around lines 78 - 81, The code logs the entire GitHub API
response body via await ghResponse.text(), which may expose sensitive data;
update the error logging in the ghResponse.ok branch (the block referencing
ghResponse and the console.error call) to avoid printing the full response
body—log the status and either omit the body or log a safely truncated snippet
(e.g., take the first N characters of the text and append "..." if longer) and
ensure you only call ghResponse.text() once into a local variable before
truncating to avoid double-reading the stream.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@worker/index.js`:
- Around line 107-109: The escapeHtml function currently replaces & < > " but
omits single quotes; update the escapeHtml function to also escape single quotes
by adding a replacement for "'" (e.g., .replace(/'/g, '&#39;' ) or &apos;) so
inputs are safe when interpolated into single-quoted attributes; ensure the new
.replace call is chained with the existing replacements in the escapeHtml
function.
- Around line 78-81: The code logs the entire GitHub API response body via await
ghResponse.text(), which may expose sensitive data; update the error logging in
the ghResponse.ok branch (the block referencing ghResponse and the console.error
call) to avoid printing the full response body—log the status and either omit
the body or log a safely truncated snippet (e.g., take the first N characters of
the text and append "..." if longer) and ensure you only call ghResponse.text()
once into a local variable before truncating to avoid double-reading the stream.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4131c3 and 5af703a.

📒 Files selected for processing (3)
  • generate-index.go
  • worker/index.js
  • worker/wrangler.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • worker/wrangler.toml

@jonathanpopham jonathanpopham merged commit 21776a3 into main Mar 2, 2026
1 of 2 checks passed
@jonathanpopham jonathanpopham deleted the feat/inline-submit branch March 2, 2026 18:10
jonathanpopham added a commit that referenced this pull request Mar 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant