From 3983b23463d3c26b255ebd0ff2956bc1019715e6 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Fri, 6 Mar 2026 18:51:17 -0700 Subject: [PATCH 1/8] feat: improve issue visibility across workspace flow - Picker page: clickable link on issue title after loading; distinguish "issue doesn't exist" from "no fork yet" on 404 from git.drupalcode.org - Template: add issue_url parameter (pre-filled by picker with drupal.org URL, visible on workspace creation form) - coder_metadata: show issue number and issue_url on workspace resource page - Startup script: fetch issue title at runtime via curl+jq; log issue URL at top of agent logs; add issue section to WELCOME.txt; pass --site-name to ddev drush si using "#NNN: " format - Fix bash syntax error: empty then-branch in if/else (caused startup script to fail on all workspaces) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- docs/drupal-issue.html | 34 ++++++-- drupal-core/template.tf | 169 ++++++++++++++++++++++++++-------------- 2 files changed, 138 insertions(+), 65 deletions(-) diff --git a/docs/drupal-issue.html b/docs/drupal-issue.html index e85fd45..3a87aff 100644 --- a/docs/drupal-issue.html +++ b/docs/drupal-issue.html @@ -448,7 +448,17 @@ <h2>Notes</h2> 'https://git.drupalcode.org/api/v4/projects/' + encodedProject + '/repository/branches?per_page=100' ); if (branchResp.status === 404) { - setStatus('No issue fork exists for #' + nid + ' yet. To work on this issue, visit the issue page on drupal.org and click "Get push access" to create a fork, then come back here.', 'info'); + // Distinguish: issue doesn't exist vs. issue exists but no fork yet + try { + const checkResp = await fetch('https://www.drupal.org/api-d7/node/' + nid + '.json'); + if (!checkResp.ok) { + setStatus('Issue #' + nid + ' does not exist on drupal.org. Check the issue number and try again.', 'error'); + } else { + setStatus('No issue fork exists for #' + nid + ' yet. To work on this issue, visit the issue page on drupal.org and click "Get push access" to create a fork, then come back here.', 'info'); + } + } catch (_) { + setStatus('No issue fork exists for #' + nid + ' yet. To work on this issue, visit the issue page on drupal.org and click "Get push access" to create a fork, then come back here.', 'info'); + } document.getElementById('load-btn').disabled = false; return; } @@ -486,8 +496,15 @@ <h2>Notes</h2> const preferred = branches.find(b => b.name.startsWith(nid + '-')); if (preferred) branchSelect.value = preferred.name; - // Set issue title - document.getElementById('issue-title-text').textContent = '#' + nid + ': ' + title; + // Set issue title with clickable link + const titleEl = document.getElementById('issue-title-text'); + titleEl.innerHTML = ''; + const issueLink = document.createElement('a'); + issueLink.href = 'https://www.drupal.org/project/drupal/issues/' + nid; + issueLink.target = '_blank'; + issueLink.textContent = '#' + nid + ': ' + title; + issueLink.style.cssText = 'color: inherit; text-decoration: underline;'; + titleEl.appendChild(issueLink); // Store and display detected Drupal version currentDrupalMajor = drupalMajor; @@ -512,7 +529,7 @@ <h2>Notes</h2> document.getElementById('load-btn').disabled = false; } - function buildCoderUrl(issueFork, issueBranch, installProfile, drupalVersion, workspaceName) { + function buildCoderUrl(issueFork, issueBranch, installProfile, drupalVersion, workspaceName, issueUrl) { const base = CODER_BASE + '/templates/' + CODER_TEMPLATE + '/workspace'; const params = new URLSearchParams({ mode: 'manual', @@ -521,6 +538,7 @@ <h2>Notes</h2> 'param.issue_branch': issueBranch, 'param.install_profile': installProfile, 'param.drupal_version': drupalVersion, + 'param.issue_url': issueUrl || '', }); return base + '?' + params.toString(); } @@ -536,7 +554,8 @@ <h2>Notes</h2> return; } - const url = buildCoderUrl(currentIssueFork, branch, profile, drupalVersion, wsName); + const issueUrl = 'https://www.drupal.org/project/drupal/issues/' + currentNid; + const url = buildCoderUrl(currentIssueFork, branch, profile, drupalVersion, wsName, issueUrl); window.open(url, '_blank'); } @@ -552,7 +571,10 @@ <h2>Notes</h2> return; } - const url = buildCoderUrl(fork, branch, profile, drupalVersion, wsName); + // For manual form, derive issue number from fork name (strip drupal- prefix) + const issueNum = fork.replace(/^drupal-/, ''); + const issueUrl = issueNum ? 'https://www.drupal.org/project/drupal/issues/' + issueNum : ''; + const url = buildCoderUrl(fork, branch, profile, drupalVersion, wsName, issueUrl); window.open(url, '_blank'); } diff --git a/drupal-core/template.tf b/drupal-core/template.tf index c9ef8a2..cb3fa9e 100644 --- a/drupal-core/template.tf +++ b/drupal-core/template.tf @@ -85,6 +85,16 @@ data "coder_parameter" "issue_branch" { order = 2 } +data "coder_parameter" "issue_url" { + name = "issue_url" + display_name = "Issue URL" + description = "Drupal.org issue page (auto-populated by the picker)." + type = "string" + default = "" + mutable = true + order = 5 +} + data "coder_parameter" "drupal_version" { name = "drupal_version" display_name = "Drupal Version" @@ -146,7 +156,9 @@ data "coder_workspace_owner" "me" {} locals { # Determine workspace home path # Sysbox Strategy: Use standard /home/coder - workspace_home = "/home/coder" + workspace_home = "/home/coder" + issue_fork_clean = trimprefix(data.coder_parameter.issue_fork.value, "drupal-") + issue_url = data.coder_parameter.issue_url.value != "" ? data.coder_parameter.issue_url.value : (local.issue_fork_clean != "" ? "https://www.drupal.org/project/drupal/issues/${local.issue_fork_clean}" : "") } locals { @@ -310,62 +322,7 @@ resource "coder_agent" "main" { # Copy files from /home/coder-files to /home/coder # The volume mount at /home/coder overrides image contents, but /home/coder-files is outside the mount echo "Copying files from /home/coder-files to ~/..." - if [ -d /home/coder-files ]; then - - - # Create Drupal-specific welcome message - if [ ! -f ~/WELCOME.txt ]; then - cat > ~/WELCOME.txt << 'WELCOME_EOF' -╔═══════════════════════════════════════════════════════════════╗ -║ Welcome to Drupal Core Development ║ -╚═══════════════════════════════════════════════════════════════╝ - -This workspace uses joachim-n/drupal-core-development-project -for a professional Drupal core development setup. - -🌐 ACCESS YOUR SITE - Click "DDEV Web" in the Coder dashboard - Or run: ddev launch - -🔐 ADMIN CREDENTIALS - Username: admin - Password: admin - One-time link: ddev drush uli - -📁 PROJECT STRUCTURE - /home/coder/drupal-core # Project root - /home/coder/drupal-core/repos/drupal # Drupal core git clone - /home/coder/drupal-core/web # Web docroot - -🛠️ USEFUL COMMANDS - ddev drush status # Check Drupal status - ddev drush uli # Get admin login link - ddev logs # View container logs - ddev ssh # SSH into web container - ddev describe # Show project details - ddev composer require ... # Add dependencies - -📚 DOCUMENTATION - Quickstart: https://github.com/ddev/coder-ddev/blob/main/docs/user/quickstart.md - DDEV: https://docs.ddev.com/ - Drupal: https://www.drupal.org/docs - Drupal API: https://api.drupal.org/ - Project Template: https://github.com/joachim-n/drupal-core-development-project - -📋 SETUP STATUS - ~/SETUP_STATUS.txt # Setup completion status - /tmp/drupal-setup.log # Detailed setup logs - -💡 TROUBLESHOOTING - If setup failed, check the status and log files above. - You can manually run setup steps from the log. - -Good luck with your Drupal core development! -WELCOME_EOF - chown coder:coder ~/WELCOME.txt 2>/dev/null || true - echo "✓ Created Drupal-specific welcome message" - fi - else + if [ ! -d /home/coder-files ]; then echo "Warning: /home/coder-files not found in image" fi @@ -573,6 +530,12 @@ STATUS_HEADER ISSUE_FORK="$${ISSUE_FORK#drupal-}" # strip leading "drupal-" if user provided it ISSUE_BRANCH="${data.coder_parameter.issue_branch.value}" INSTALL_PROFILE="${data.coder_parameter.install_profile.value}" + + # Fetch issue title from drupal.org API at runtime (best-effort; empty string on failure) + ISSUE_TITLE="" + if [ -n "$ISSUE_FORK" ]; then + ISSUE_TITLE=$(curl -sf "https://www.drupal.org/api-d7/node/$${ISSUE_FORK}.json" 2>/dev/null | jq -r '.title // ""' 2>/dev/null || echo "") + fi USING_ISSUE_FORK=false SETUP_FAILED=false if [ -n "$ISSUE_FORK" ] || [ -n "$ISSUE_BRANCH" ]; then @@ -580,6 +543,76 @@ STATUS_HEADER log_setup "Issue fork mode: ISSUE_FORK=$ISSUE_FORK ISSUE_BRANCH=$ISSUE_BRANCH INSTALL_PROFILE=$INSTALL_PROFILE" fi + # Log issue link early so it's visible at the top of the agent logs + if [ -n "$ISSUE_FORK" ]; then + log_setup "🔗 Issue: https://www.drupal.org/project/drupal/issues/$ISSUE_FORK" + if [ -n "$ISSUE_TITLE" ]; then + log_setup " Title: $ISSUE_TITLE" + fi + fi + + # Create Drupal-specific welcome message (first run only, now that issue info is available) + if [ ! -f ~/WELCOME.txt ]; then + { + cat << 'WELCOME_STATIC' +╔═══════════════════════════════════════════════════════════════╗ +║ Welcome to Drupal Core Development ║ +╚═══════════════════════════════════════════════════════════════╝ + +This workspace uses joachim-n/drupal-core-development-project +for a professional Drupal core development setup. + +🌐 ACCESS YOUR SITE + Click "DDEV Web" in the Coder dashboard + Or run: ddev launch + +🔐 ADMIN CREDENTIALS + Username: admin + Password: admin + One-time link: ddev drush uli + +📁 PROJECT STRUCTURE + /home/coder/drupal-core # Project root + /home/coder/drupal-core/repos/drupal # Drupal core git clone + /home/coder/drupal-core/web # Web docroot + +🛠️ USEFUL COMMANDS + ddev drush status # Check Drupal status + ddev drush uli # Get admin login link + ddev logs # View container logs + ddev ssh # SSH into web container + ddev describe # Show project details + ddev composer require ... # Add dependencies + +📚 DOCUMENTATION + Quickstart: https://github.com/ddev/coder-ddev/blob/main/docs/user/quickstart.md + DDEV: https://docs.ddev.com/ + Drupal: https://www.drupal.org/docs + Drupal API: https://api.drupal.org/ + Project Template: https://github.com/joachim-n/drupal-core-development-project + +📋 SETUP STATUS + ~/SETUP_STATUS.txt # Setup completion status + /tmp/drupal-setup.log # Detailed setup logs + +💡 TROUBLESHOOTING + If setup failed, check the status and log files above. + You can manually run setup steps from the log. + +Good luck with your Drupal core development! +WELCOME_STATIC + + if [ -n "$ISSUE_FORK" ]; then + echo "" + echo "🐛 WORKING ON ISSUE" + echo " #$${ISSUE_FORK}: $${ISSUE_TITLE}" + echo " https://www.drupal.org/project/drupal/issues/$${ISSUE_FORK}" + fi + } > ~/WELCOME.txt + chown coder:coder ~/WELCOME.txt 2>/dev/null || true + echo "✓ Created Drupal-specific welcome message" + fi + # Step 4: Set up Drupal core project — use seed cache when available (fast path) # Issue forks skip the cache: the seed composer.json requires "drupal/core: dev-main" and # vendor is resolved for PHP 8.5/drupal12, both incompatible with non-main issue branches. @@ -854,6 +887,16 @@ STATUS_HEADER # - No issue fork (issue code may differ from cached DB) # - Install profile is demo_umami (cache was built with that profile) # - Cache tarball exists + + # Compute site name for drush si (used when running a full install) + if [ -n "$ISSUE_FORK" ] && [ -n "$ISSUE_TITLE" ]; then + SITE_NAME="#$${ISSUE_FORK}: $${ISSUE_TITLE}" + elif [ -n "$ISSUE_FORK" ]; then + SITE_NAME="Issue #$${ISSUE_FORK}" + else + SITE_NAME="Drupal Core Development" + fi + if ddev drush status 2>/dev/null | grep -q "Drupal bootstrap.*Successful"; then log_setup "✓ Drupal already installed" update_status "✓ Drupal install: Already present" @@ -874,7 +917,7 @@ STATUS_HEADER log_setup "⚠ DB import failed ($((SECONDS - _t))s), falling back to full site install..." update_status "⚠ DB import failed, running full install..." _t=$SECONDS - if ddev drush si -y "$INSTALL_PROFILE" --account-pass=admin >> "$SETUP_LOG" 2>&1; then + if ddev drush si -y "$INSTALL_PROFILE" --account-pass=admin --site-name="$SITE_NAME" >> "$SETUP_LOG" 2>&1; then log_setup "✓ Drupal installed successfully (fallback, $((SECONDS - _t))s)" update_status "✓ Drupal install: Success (fallback)" else @@ -891,7 +934,7 @@ STATUS_HEADER fi update_status "⏳ Drupal install: In progress..." - if ddev drush si -y "$INSTALL_PROFILE" --account-pass=admin >> "$SETUP_LOG" 2>&1; then + if ddev drush si -y "$INSTALL_PROFILE" --account-pass=admin --site-name="$SITE_NAME" >> "$SETUP_LOG" 2>&1; then log_setup "✓ Drupal installed ($((SECONDS - _t))s)" log_setup "" log_setup " Admin Credentials:" @@ -1362,6 +1405,14 @@ resource "coder_metadata" "workspace_info" { key = "image" value = "${docker_image.workspace_image.name} (version: ${local.image_version})" } + item { + key = "issue" + value = local.issue_fork_clean != "" ? "#${local.issue_fork_clean}" : "(standard workspace)" + } + item { + key = "issue_url" + value = local.issue_url + } } # Output for Vault integration status (visible in Terraform logs) From cfd9f1399818b9d162328275534e66cdaecec3c2 Mon Sep 17 00:00:00 2001 From: Randy Fay <randy@randyfay.com> Date: Fri, 6 Mar 2026 19:01:54 -0700 Subject: [PATCH 2/8] fix: remove issue_url parameter from workspace creation form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Coder has no display-only parameter type — all coder_parameters render as editable text inputs with URL autofill etc., which is confusing UX. Remove issue_url as a parameter entirely; compute it from issue_fork in locals instead. The issue URL is still shown on the workspace resource page via coder_metadata, in WELCOME.txt, and in startup logs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- docs/drupal-issue.html | 11 +++-------- drupal-core/template.tf | 11 +---------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/docs/drupal-issue.html b/docs/drupal-issue.html index 3a87aff..118bc7b 100644 --- a/docs/drupal-issue.html +++ b/docs/drupal-issue.html @@ -529,7 +529,7 @@ <h2>Notes</h2> document.getElementById('load-btn').disabled = false; } - function buildCoderUrl(issueFork, issueBranch, installProfile, drupalVersion, workspaceName, issueUrl) { + function buildCoderUrl(issueFork, issueBranch, installProfile, drupalVersion, workspaceName) { const base = CODER_BASE + '/templates/' + CODER_TEMPLATE + '/workspace'; const params = new URLSearchParams({ mode: 'manual', @@ -538,7 +538,6 @@ <h2>Notes</h2> 'param.issue_branch': issueBranch, 'param.install_profile': installProfile, 'param.drupal_version': drupalVersion, - 'param.issue_url': issueUrl || '', }); return base + '?' + params.toString(); } @@ -554,8 +553,7 @@ <h2>Notes</h2> return; } - const issueUrl = 'https://www.drupal.org/project/drupal/issues/' + currentNid; - const url = buildCoderUrl(currentIssueFork, branch, profile, drupalVersion, wsName, issueUrl); + const url = buildCoderUrl(currentIssueFork, branch, profile, drupalVersion, wsName); window.open(url, '_blank'); } @@ -571,10 +569,7 @@ <h2>Notes</h2> return; } - // For manual form, derive issue number from fork name (strip drupal- prefix) - const issueNum = fork.replace(/^drupal-/, ''); - const issueUrl = issueNum ? 'https://www.drupal.org/project/drupal/issues/' + issueNum : ''; - const url = buildCoderUrl(fork, branch, profile, drupalVersion, wsName, issueUrl); + const url = buildCoderUrl(fork, branch, profile, drupalVersion, wsName); window.open(url, '_blank'); } diff --git a/drupal-core/template.tf b/drupal-core/template.tf index cb3fa9e..ffade6d 100644 --- a/drupal-core/template.tf +++ b/drupal-core/template.tf @@ -85,15 +85,6 @@ data "coder_parameter" "issue_branch" { order = 2 } -data "coder_parameter" "issue_url" { - name = "issue_url" - display_name = "Issue URL" - description = "Drupal.org issue page (auto-populated by the picker)." - type = "string" - default = "" - mutable = true - order = 5 -} data "coder_parameter" "drupal_version" { name = "drupal_version" @@ -158,7 +149,7 @@ locals { # Sysbox Strategy: Use standard /home/coder workspace_home = "/home/coder" issue_fork_clean = trimprefix(data.coder_parameter.issue_fork.value, "drupal-") - issue_url = data.coder_parameter.issue_url.value != "" ? data.coder_parameter.issue_url.value : (local.issue_fork_clean != "" ? "https://www.drupal.org/project/drupal/issues/${local.issue_fork_clean}" : "") + issue_url = local.issue_fork_clean != "" ? "https://www.drupal.org/project/drupal/issues/${local.issue_fork_clean}" : "" } locals { From 8ab9caaa93203d591fcc7b8263916c545b0bf6ca Mon Sep 17 00:00:00 2001 From: Randy Fay <randy@randyfay.com> Date: Fri, 6 Mar 2026 19:28:36 -0700 Subject: [PATCH 3/8] docs: document issue visibility features in quickstart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- docs/user/quickstart.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/user/quickstart.md b/docs/user/quickstart.md index c47fcf9..3c859ed 100644 --- a/docs/user/quickstart.md +++ b/docs/user/quickstart.md @@ -68,6 +68,12 @@ ddev ssh The fastest way: use the **[Drupal Issue Picker](https://start.coder.ddev.com/drupal-issue)**. Paste a drupal.org issue URL or bare issue number — it fetches the available branches, lets you pick one, and opens a pre-configured workspace with the issue branch already checked out and all Composer dependencies resolved for that branch. +When working on an issue, the workspace surfaces issue info in several places: + +- **Workspace resource page** — the `issue_url` metadata item links directly to the drupal.org issue +- **`~/WELCOME.txt`** — shows the issue number, title, and URL +- **Drupal site name** — set to `#NNNN: issue title` during install (visible in the site header) + To push your changes back: ```bash From 2e20f4c41bcd34219c8a49b7275dae3a63a95d5e Mon Sep 17 00:00:00 2001 From: Randy Fay <randy@randyfay.com> Date: Fri, 6 Mar 2026 19:38:52 -0700 Subject: [PATCH 4/8] fix: correctly detect non-existent drupal.org issues in picker Validate the API response body (checking nid match) instead of trusting checkResp.ok alone, and change the catch fallback to an error rather than the misleading "no fork yet" message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- docs/drupal-issue.html | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/drupal-issue.html b/docs/drupal-issue.html index 118bc7b..569dd70 100644 --- a/docs/drupal-issue.html +++ b/docs/drupal-issue.html @@ -454,10 +454,17 @@ <h2>Notes</h2> if (!checkResp.ok) { setStatus('Issue #' + nid + ' does not exist on drupal.org. Check the issue number and try again.', 'error'); } else { - setStatus('No issue fork exists for #' + nid + ' yet. To work on this issue, visit the issue page on drupal.org and click "Get push access" to create a fork, then come back here.', 'info'); + // Verify the response is actually a valid node (not a redirect or error page) + let nodeData; + try { nodeData = await checkResp.json(); } catch (_) { nodeData = null; } + if (!nodeData || String(nodeData.nid) !== String(nid)) { + setStatus('Issue #' + nid + ' does not exist on drupal.org. Check the issue number and try again.', 'error'); + } else { + setStatus('No issue fork exists for #' + nid + ' yet. To work on this issue, visit the issue page on drupal.org and click "Get push access" to create a fork, then come back here.', 'info'); + } } } catch (_) { - setStatus('No issue fork exists for #' + nid + ' yet. To work on this issue, visit the issue page on drupal.org and click "Get push access" to create a fork, then come back here.', 'info'); + setStatus('Issue #' + nid + ' does not appear to exist on drupal.org. Check the issue number and try again.', 'error'); } document.getElementById('load-btn').disabled = false; return; From fb0b91e1bd8caede7efe778213e3f46395c17f9b Mon Sep 17 00:00:00 2001 From: Randy Fay <randy@randyfay.com> Date: Fri, 6 Mar 2026 19:43:25 -0700 Subject: [PATCH 5/8] fix: detect non-core drupal.org issues in picker Check field_project.machine_name so contrib project issues show a clear error instead of the misleading "no fork yet" message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- docs/drupal-issue.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/drupal-issue.html b/docs/drupal-issue.html index 569dd70..6d424d9 100644 --- a/docs/drupal-issue.html +++ b/docs/drupal-issue.html @@ -457,8 +457,11 @@ <h2>Notes</h2> // Verify the response is actually a valid node (not a redirect or error page) let nodeData; try { nodeData = await checkResp.json(); } catch (_) { nodeData = null; } + const projectName = nodeData && nodeData.field_project && nodeData.field_project.machine_name; if (!nodeData || String(nodeData.nid) !== String(nid)) { setStatus('Issue #' + nid + ' does not exist on drupal.org. Check the issue number and try again.', 'error'); + } else if (projectName && projectName !== 'drupal') { + setStatus('Issue #' + nid + ' is a ' + projectName + ' issue, not a Drupal core issue. This picker only supports Drupal core.', 'error'); } else { setStatus('No issue fork exists for #' + nid + ' yet. To work on this issue, visit the issue page on drupal.org and click "Get push access" to create a fork, then come back here.', 'info'); } From c2ab9f3cf19f527ea4dcbd8aee10d75acace97c8 Mon Sep 17 00:00:00 2001 From: Randy Fay <randy@randyfay.com> Date: Fri, 6 Mar 2026 19:46:26 -0700 Subject: [PATCH 6/8] fix: rename picker to "Drupal Core Issue Picker" and improve wording Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --- docs/drupal-issue.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/drupal-issue.html b/docs/drupal-issue.html index 6d424d9..f0c28ec 100644 --- a/docs/drupal-issue.html +++ b/docs/drupal-issue.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Drupal Issue Picker — DDEV Coder Workspaces + Drupal Core Issue Picker — DDEV Coder Workspaces diff --git a/docs/index.html b/docs/index.html index 0b87800..09609fd 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,6 +4,7 @@ DDEV Coder Workspaces — Drupal Core Development +