Skip to content

feat: add Coder Quickstart base to the template builder - #27247

Merged
nickvigilante merged 15 commits into
mainfrom
vigilante/docs-558-add-coder-quickstart-template-to-template-builder
Aug 27, 2026
Merged

nickvigilante merged 15 commits into
mainfrom
vigilante/docs-558-add-coder-quickstart-template-to-template-builder

Conversation

@nickvigilante

@nickvigilante nickvigilante commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the Coder Quickstart template as a selectable base in the template builder guided wizard. It is a Docker-based starter that lets a user pick languages and optionally clone a repo.

What changed

  • New base package coderd/templatebuilder/bases/quickstart/:
    • base.json: id: "quickstart", display_name: "Coder Quickstart", os: "linux".
    • main.tf.tmpl: a Docker workspace with a language selector, a single language-install script, an optional Git clone, and workspace presets. Editors are added via the builder's module step rather than baked into the base.
    • install-languages.sh.tftpl and README.md (with prerequisites markers so the builder can extract the prerequisites section).
  • Placement: the bases endpoint returns a plain list sorted by display name (with ID as a deterministic tiebreak). The wizard's base-infra select step prioritizes the Quickstart and Docker starters to the front on the client, mirroring the existing client-side module prioritization in ModuleSelectStep (shared sortByPriority helper).
  • Base/module collision guard: a base's bundled catalog modules are derived from its rendered Terraform (ExtractModuleNames, cached with sync.OnceValues and filtered to catalog IDs). validateModules seeds its seen-set from that derived set, so a wizard-selected module the base already renders is rejected with a clear error, and the modules endpoint omits it so the wizard never offers a colliding module. The base's Terraform is the single source of truth, so there is no separate manifest list that can drift.

Testing

  • go test ./coderd/ (template builder handler: bases ordering, the baseSpec table, the modules-endpoint base filter): pass.
  • go test ./coderd/templatebuilder/ (all-bases render/snapshot, the collision guard, TestBaseIncludedModules, the selector/install-script drift test): pass.
  • gofmt, go vet, golangci-lint: clean.
  • Frontend tsc, biome, and knip: clean.

Review decisions

  • Template scope: trimmed to infrastructure, a language selector, and an optional Git clone. The IDE selector was removed; editors are added via the builder's module step.
  • Placement: base ordering is presentational, so it lives on the client base-infra select step rather than in the API.
  • git-clone in the base: kept for the build-time clone affordance. The base renders its module source verbatim, so it currently pins the public registry and does not yet honor a deployment's registry mirror; that fix is tracked in DOCS-610 and delivered as a stacked follow-up PR (an in-code note documents the current behavior).
  • Container image: left hardcoded (codercom/enterprise-base:ubuntu). The quickstart base is the opinionated path; the Docker base covers custom images. Revisiting a container_image variable is tracked in DOCS-611.

Review updates (@jeremyruppel)

  • Ordering moved to the client: deleted the server-side Quickstart-before-Docker grouping (and its position test); the base-infra select step now prioritizes bases client-side via a shared sortByPriority helper, following the ModuleSelectStep precedent. The API just returns a stable, name-sorted list.
  • Derive included modules from render: dropped the included_modules manifest field and the IncludedModules struct field; BaseIncludedModules now derives from the rendered base (single source of truth). Removed TestBaseIncludedModulesMatchRendered and added a direct TestBaseIncludedModules.
  • Kept the PR scoped to "add quickstart base": reverted ExtractAgentResourceName/ExtractModuleNames to the pre-hclsyntax regex extraction and removed the option/preset HCL extractor family; the selector↔install-script drift test now uses a small regex. The broader templatebuilder HCL migration and the preset-language subset guard are tracked in DOCS-735.
  • Branch rebased onto origin/main (no content changes beyond the above).

Linear: DOCS-558

This PR was created with AI assistance (Coder Agents).

@linear-code

linear-code Bot commented Jul 14, 2026

Copy link
Copy Markdown

DOCS-558

@nickvigilante

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-08-24 15:58 UTC by @nickvigilante
Spend: $56.63 / $100.00

Review history
  • R1 (2026-07-14), 1 P0, COMMENT. Review
  • R2 (2026-07-14): 12 reviewers, 5 Note, 1 P0, 1 P1, 2 P2, 1 P3, REQUEST_CHANGES. Review
  • R3 (2026-07-15), 5 Note, 1 P0, 1 P1, 2 P2, 1 P3, COMMENT. Review
  • R4 (2026-07-21): 16 reviewers, 1 Nit, 6 Note, 1 P0, 1 P1, 3 P2, 5 P3, REQUEST_CHANGES. Review
  • R5 (2026-08-04): 12 reviewers, 2 Nit, 8 Note, 1 P0, 1 P1, 3 P2, 7 P3, COMMENT. Review
  • R6 (2026-08-04): 12 reviewers, 2 Nit, 9 Note, 1 P0, 1 P1, 3 P2, 8 P3, COMMENT. Review
  • R7 (2026-08-24): 12 reviewers, 4 Nit, 11 Note, 1 P0, 1 P1, 3 P2, 8 P3, COMMENT. Review
  • R8 (2026-08-24): 12 reviewers, 5 Nit, 13 Note, 1 P0, 1 P1, 3 P2, 8 P3, COMMENT. Review
  • R9 (2026-08-24): 10 reviewers, 5 Nit, 13 Note, 1 P0, 1 P1, 3 P2, 8 P3, APPROVE. Review

deep-review v0.9.0 | Round 9 | b6d7653..3ccaf2c

Last posted: Round 9, 31 findings (1 P0, 1 P1, 3 P2, 8 P3, 5 Nit, 13 Note), APPROVE. Review

Finding inventory

Finding inventory - PR #27247

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P0 Author fixed (58eb2c9) coderd/templatebuilder_handler.go:91 Quickstart-first pin breaks pre-existing TestTemplateBuilderBases/Sorted R1 Netero Yes
CRF-2 Note Author fixed (58eb2c9) coderd/templatebuilder_handler_test.go:110 No positive test asserts the new quickstart-first ordering; description's test claim ran the wrong package R1 Netero Yes (body)
CRF-3 Note Author acknowledged R3; base rewritten R4 (re-evaluate) coderd/templatebuilder/testdata/quickstart.tf.golden:1 Golden is byte-identical to embedded main.tf.tmpl (directive-free); snapshot asserts source==source. Low value, not vacuous R2 Netero, Law Yes
CRF-4 P1 Author fixed (30fb31b); verified R4 (instance); class durability tracked as CRF-12 coderd/templatebuilder/bases/quickstart/main.tf.tmpl:273 Base embeds catalog-named module blocks; selecting the same module in the wizard yields a duplicate module call and a broken template. No collision guard in validateModules R2 Hisoka P1, Mafuuu P1, Pariston P1, Luffy P1, Meruem P1, Bisky P2 Yes
CRF-5 P2 Author fixed (17186ff); verified R4 coderd/templatebuilder/bases/quickstart/main.tf.tmpl:118 JetBrains IDE selector + preset + README promise install, but the jetbrains module is commented out; silent no-op R2 Leorio P2, Hisoka Note Yes
CRF-6 P2 Deferred (DOCS-610; stacked follow-up PR; in-code note added 658b74b) coderd/templatebuilder/bases/quickstart/main.tf.tmpl:157 Base git-clone module hardcodes registry.coder.com; BaseRenderContext structurally cannot honor RegistryURL; mirror/air-gapped git_repo builds fail terraform init R2 Mafuuu P2, Chopper P2, Melody P2, Meruem P2, Luffy P2, Knov P2, Pariston P3, Hisoka Note, Zoro Note Yes
CRF-7 Note Author accepted R5 (DOCS-611; opinionated starter, baseSpec locks no vars) coderd/templatebuilder/bases/quickstart/base.json:5 Container image hardcoded while sibling bases expose a container_image variable (open q#3) R2 Mafuuu, Luffy Yes
CRF-8 Note Superseded by R4 ordering change; panel closed R4 (mechanism now groupQuickstartBeforeDocker, proportional) coderd/templatebuilder_handler.go:87 Quickstart-first ordering is a hardcoded ID pin in the comparator, not data (open q#2). Proportional for a single pin R2 Pariston, Meruem Yes
CRF-9 Note Author fixed (76aabd3); verified R4; modernization half tracked as CRF-18 coderd/templatebuilder_handler.go:91 Comparator uses unstable reflection-based sort.Slice; not total (no ID tiebreak). Consider slices.SortFunc + tiebreak on ID R2 Komugi Note, ging-go Nit Yes
CRF-10 P3 Author fixed main.tf.tmpl (17186ff); author contested R5; panel closed R5 (8/8 accept: base diverged, drift covered by CRF-12) coderd/templatebuilder/bases/quickstart/install-languages.sh.tftpl:1 Base is a byte-for-byte copy of the already-embedded example; nothing guards drift R2 Robin Yes
CRF-11 Note Author fixed (b64ceea); verified R4 coderd/templatebuilder_handler_test.go:44 Quickstart absent from handler specs table; API surface only checked by generic loops R2 Bisky Yes
CRF-12 P3 Author fixed (657abbb); panel verified R5; default-context limitation -> CRF-21 coderd/templatebuilder/bases/quickstart/base.json:6 included_modules is a hand-maintained duplicate of the base's rendered module blocks; nothing enforces the mirror, so drift silently re-opens CRF-4 (P1). No catalog-ID validation either R4 Hisoka P3, Mafuuu P3, Pariston P3, Chopper P3, Melody P3, Meruem P3, Zoro P3, Knov P2 Yes
CRF-13 P2 Author fixed (658b74b, doc); panel verified R5; behavioral residual -> CRF-23 coderd/templatebuilder/bases/quickstart/README.md:49 README promises languages are cached for subsequent starts, but installs land on the ephemeral container and reinstall every start (blocking login); fails on mirror/air-gapped R4 Leorio P2, Meruem P2 Yes
CRF-14 P3 Author fixed (658b74b); panel verified R5 coderd/templatebuilder/bases/quickstart/install-languages.sh.tftpl:14 Language dispatch uses unanchored grep substring match; safe only because current values are substring-disjoint; a future value silently mis-installs R4 Meruem P3, Hisoka Note, Mafuuu Note, Knov Note Yes
CRF-15 P3 Author fixed (657abbb); panel verified R5 coderd/templatebuilder_handler_test.go:224 BaseExcludesIncludedModules quickstart loop is a vacuous negative loop; passes on an empty list. Add require.NotEmpty R4 Bisky Yes
CRF-16 P3 Author fixed (1cf64f1); panel verified R5; minor comment residual -> CRF-24 coderd/templatebuilder_handler.go:87 Base/module collision rationale duplicated ~6 times across the diff; comments narrate the code and will drift R4 Gon P2 Yes
CRF-17 Note Author fixed (1cf64f1); panel verified R5 coderd/templatebuilder/compose.go:236 Conflict loop iterates only requested modules, so a base-included module's own ConflictsWith is never enforced; the "bidirectional" comment overpromises. Latent R4 Mafuuu, Melody Yes
CRF-18 Nit Author fixed (1cf64f1); panel verified R5 coderd/templatebuilder_handler.go:91 Go modernization: slices.SortFunc + cmp.Or, slices.IndexFunc, slices.Insert over hand-rolled sort/search/splice R4 ging-go Nit, Robin Nit Yes
CRF-19 Note Author fixed (d91e8d5); panel verified closed R7 (hclsyntax rewrite; TestExtractHCLHelpers mutation-tested) coderd/templatebuilder/render.go:174 ExtractParameterOptionValues/ExtractModuleNames hand-roll HCL parsing (brace-depth scan desyncs on braces-in-strings/heredocs; regexes don't skip comments), untested. Test-only, nil->require.NotEmpty fails loud. Fix: use hclsyntax (already imported) or a table test R5 Netero, Bisky, Mafu-san, Mafuuu, Zoro, Hisoka (Note); Robin (P3 structural) Yes
CRF-20 P3 Author fixed (bc740d2); panel verified closed R6 (mutation-tested) coderd/templatebuilder/bases/quickstart/main.tf.tmpl:44 Language selector option values and install-script has_language branches are hand-maintained duplicates across two files with no cross-check test; drift silently no-ops a selected language (CRF-5 class). Same shape CRF-12 just guarded for included_modules R5 Bisky P3 Yes
CRF-21 Note Author addressed (bc740d2); panel verified accurate R6 coderd/templatebuilder/compose_test.go:575 CRF-12 invariant test renders with DefaultBaseRenderContext only; production renderBase overlays merged variables, so a variable-gated catalog module block would evade the guard. Dormant today (quickstart has no vars) R5 Hisoka Yes
CRF-22 Note Author fixed (bc740d2); panel verified accurate R6 coderd/templatebuilder/bases/quickstart/README.md:49 CRF-13 fix's leading "reinstalled on every start" is not literally true for Rust ($HOME/.cargo persists); the trailing "most" hedges it. Leorio (docs) considers current wording acceptable R5 Mafuuu Note; Leorio/Hisoka disagree (acceptable) Yes
CRF-23 P3 Deferred (DOCS-614, Medium; distinct from DOCS-610) coderd/templatebuilder/bases/quickstart/main.tf.tmpl:141 CRF-13's doc fix is honest but the behavioral residual (reinstall-every-start blocks login; fails on network-restricted deployments) is untracked; DOCS-610 covers git-clone registry, not this. Needs human decision: track or accept R5 Mafu-san P3 Yes
CRF-24 Nit Author fixed (bc740d2); panel verified closed R6 coderd/templatebuilder_handler.go:90 CRF-16 residual: the sort-site comment still repeats the groupQuickstartBeforeDocker placement rationale verbatim R5 Gon Nit Yes
CRF-25 P3 Author fixed (d91e8d5); panel verified closed R7 (per-preset require.Subset; mutation-tested) coderd/templatebuilder/bases/quickstart/main.tf.tmpl:174 The four coder_workspace_preset blocks are a third hand-maintained list of language values; the CRF-20 guard binds only selector<->script, leaving presets unbound. A preset naming a drifted value silently no-ops (or fails at build). Extend the test to assert each preset's languages subset of selector options R6 Pariston P3, Melody P3 Yes
CRF-27 Note Author fixed (dad3379); panel verified closed R8 (hclsyntax port; CountAfterNestedBlock mutation-tested) coderd/templatebuilder/render.go:110 ExtractAgentResourceName (production, compose.go:71) still uses the brace-scan regex class CRF-19 removed from its test-only siblings; agentCountPattern [^}]* misses count/for_each after a nested block, and a miss produces a wrong coder_agent..id in composed modules (does not fail loud). Pre-existing (#26901), dormant. Migrate to hclsyntax or ticket R7 Meruem, Pariston Yes
CRF-28 Note Author fixed (dad3379); panel verified closed R8 (require.Len count guard binds; mutation-tested) coderd/templatebuilder/render.go:236 ExtractPresetParameterValues silently skips any preset whose languages is not a static string list, so a future non-literal preset evades the CRF-25 subset guard while the test still passes. Dormant (all 4 presets are jsonencode literals). Assert extracted count == preset-block count R7 Mafuuu, Melody Yes
CRF-29 Nit Author fixed (dad3379); panel verified closed R8 (no CRF- in shipped code) coderd/templatebuilder/compose_test.go:637 A // CRF-25: comment leaks a review-panel finding ID into shipped test code (meaningless to a repo reader; AGENTS.md forbids review-artifact comments). Drop the prefix, keep the inline explanation. Gon rated P2; adjusted to Nit (convention violation, no runtime consequence) R7 Gon P2 Yes
CRF-30 Nit Author fixed (dad3379); panel verified closed R8 coderd/templatebuilder/compose_test.go:650 TestExtractHCLHelpers doc narrates the removed regex/brace-scan implementation (history of the edit) rather than the behavior under test, and is incomplete. AGENTS.md: explain behavior, not edit history R7 Gon Nit Yes
CRF-31 Nit Author fixed (3ccaf2c); panel verified closed R9 coderd/templatebuilder/render.go:110 ExtractAgentResourceName doc's trailing clause ("not only before the first closing brace") narrates the removed brace-scan regex, not current behavior; a new instance of the CRF-30 class introduced in the CRF-27 fix. Delete the clause R8 Gon Yes
CRF-32 Note Author fixed (3ccaf2c); panel verified closed R9 coderd/templatebuilder/render.go:150 parseHCLBody doc justifies nil-on-error by pointing only at test assertions, but after the CRF-27 port its one production caller (ExtractAgentResourceName) turns nil into an error; the safety rationale omits the most important consumer R8 Leorio Yes
CRF-33 Nit Author acknowledged R9 (PR squash-merges, review-round subjects moot; head commit subject descriptive) (commit subjects) Review-round commit subjects ("address R7 review nits") leak review-process bookkeeping, meaningless post-merge (same class as CRF-29); bodies are good. Conditional on squash-merge R8 Leorio Yes (body)
CRF-34 Note Author fixed (3ccaf2c); panel verified closed R9 (predicate aligned to != 2 across all four extractors) coderd/templatebuilder/render.go:227 ExtractPresetParameterValues selects preset blocks with len(block.Labels) < 1 while sibling ExtractPresetNames/ExtractParameterOptionValues use == 2; the CRF-28 count guard's two halves rely on the HCL invariant rather than a shared predicate. Inert today (coder_workspace_preset always has 2 labels) R8 Mafu-san, Melody Yes

Law analysis

  • Effective LOC: +1049 -1 (8 files); ~13 lines of novel logic, rest is verbatim-ported template data plus a byte-identical golden duplicate.
  • Head SHA: 3a6ad9a
  • Verdict: Don't split.
  • Enforcement: Advisory (no action). Panel proceeds.
  • Panel note (for future panel round): testdata/quickstart.tf.golden is byte-for-byte identical to bases/quickstart/main.tf.tmpl (template has no directives, so render is identity). The snapshot test therefore asserts "the embedded file equals the embedded file rendered." Weak but not vacuous; catches accidental corruption of the embedded template. Worth a panel look on the next round.

Contested and acknowledged

CRF-10 (P3, install-languages.sh.tftpl) - base-vs-example verbatim-copy drift

  • Finding (R2, Robin): the base file was a byte-for-byte copy of examples/templates/quickstart/install-languages.sh.tftpl with no test guarding drift.
  • Author defense (R5): after 658b74b the base file was rewritten (dispatch changed to has_language) so it is no longer byte-identical to the example; the base is intentionally its own artifact; a base==example test would wrongly block legitimate divergence; the real manifest-vs-rendered drift risk is covered by the new CRF-12 invariant test.
  • Panel closure (R5, 8/8 accept): Netero, Hisoka, Mafu-san, Mafuuu, Pariston, Robin, Melody, Razor each diffed the two files, confirmed genuine divergence (base uses has_language, example still uses grep -q), and agreed a base==example guard would block legitimate divergence while the real drift is caught by CRF-12. Closed. (Sibling note, out of PR scope: the example file retains the old unanchored-grep CRF-14 bug; port has_language to the example in its own change.)

Round log

Round 9

Churn guard: PROCEED. Author pushed 3ccaf2c (rebased onto b6d7653). Panel (10: Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Gon, Leorio, Melody, Komugi + wildcard Zoro) + Netero verified CRF-31/32/34 closed and CRF-33 acknowledged (squash-merge moot). NO NEW FINDINGS from any reviewer, first fully clean pass. Mafu-san confirmed CRF-34 was generalized across all four extractor predicates, not patched at one site; Netero confirmed the MCP-server churn in the changed-files list is upstream rebase noise, not this PR's diff. All prior findings resolved or deferred-with-ticket (CRF-6/DOCS-610, CRF-7/DOCS-611, CRF-23/DOCS-614; CRF-3 acknowledged). Event APPROVE (downgraded to COMMENT) + dismiss stale CHANGES_REQUESTED. Review converged: no blocking findings remain. Reviewed against b6d7653..3ccaf2c.

Round 8

Churn guard: PROCEED. Author pushed dad3379 (PR rebased onto a48aedd). All 4 R7 findings addressed and verified closed by Panel (12) + Netero: CRF-27 (production ExtractAgentResourceName ported off the regex/brace-scan onto shared hclsyntax parseHCLBody, iterating block attributes for count/for_each; deleted agentResourcePattern/agentCountPattern and the regexp import; TestExtractAgentResourceName/CountAfterNestedBlock mutation-tested against the old pattern), CRF-28 (ExtractPresetNames + require.Len count guard binds; mutation-tested), CRF-29 (no CRF- in shipped code), CRF-30 (doc rewritten). No new P0/P1/P2/P3. New (all cosmetic/inert): CRF-31 (Nit, Gon: CRF-27 doc reintroduced edit-history narration, CRF-30 class), CRF-32 (Note, Leorio: parseHCLBody doc omits its production caller), CRF-33 (Nit, Leorio: review-bookkeeping commit subjects; body), CRF-34 (Note, Mafu-san+Melody: inconsistent preset label predicate < 1 vs == 2, inert). CRF-3 golden panel question closed (Bisky: low-value-not-vacuous, no action). Verified CRF-31/CRF-34 in worktree. Do not re-raise CRF-6 (DOCS-610), CRF-7 (DOCS-611), CRF-23 (DOCS-614). Event COMMENT + dismiss stale CHANGES_REQUESTED. PR is essentially ready; only cosmetic polish remains. Reviewed against a48aedd..dad3379.

Round 7

Churn guard: PROCEED. Author pushed d91e8d5 (PR rebased onto 6a58d50). Both R6 findings addressed: CRF-25 (per-preset require.Subset assertion + negation test in TestQuickstartLanguageSelectorMatchesInstallScript) and CRF-19 (rewrote ExtractModuleNames, ExtractParameterOptionValues, and new ExtractPresetParameterValues to parse via hashicorp/hcl/v2 hclsyntax instead of regex/brace-scan; added TestExtractHCLHelpers covering braces-in-strings, commented options, jsonencode unwrapping). Panel to verify the hclsyntax rewrite's parsing correctness, ExtractPresetParameterValues, and the preset subset guard. Do not re-raise CRF-6 (DOCS-610), CRF-7 (DOCS-611), CRF-23 (DOCS-614). Reviewed against 6a58d50..d91e8d5.
Panel (12) + Netero verified CRF-19 and CRF-25 closed (both mutation-tested by multiple reviewers). No new P0/P1/P2. New: CRF-27 (Note, Meruem+Pariston: production ExtractAgentResourceName regex is the last CRF-19-class instance; pre-existing #26901, dormant, doesn't fail loud), CRF-28 (Note, Mafuuu+Melody: ExtractPresetParameterValues skips non-literal presets, CRF-25 guard scope gap), CRF-29 (Nit, Gon P2->Nit: leaked // CRF-25: review-ID comment), CRF-30 (Nit, Gon: test doc narrates removed implementation). All verified in worktree. Event COMMENT + dismiss stale CHANGES_REQUESTED.

Round 6

Churn guard: PROCEED. Author pushed bc740d2 (PR rebased onto 1142706). Panel (12) + Netero verified CRF-20/21/22/24 closed (CRF-20 mutation-tested by 6 reviewers; CRF-20 = ExtractParameterOptionValues + TestQuickstartLanguageSelectorMatchesInstallScript, CRF-21 = documented default-context limitation, CRF-22 = README names Rust exception, CRF-24 = sort-site comment reduced). CRF-23 deferred (DOCS-614), CRF-6/7 tracked, out-of-scope example grep tracked (DOCS-613), slices.IsSorted declined with reasoning. New: CRF-25 (P3, presets are an unguarded third language list; Pariston + Melody convergent; verified 4 presets, no preset test). CRF-19 (Note): the test-only ExtractParameterOptionValues/ExtractModuleNames hand-roll HCL parsing with untested brace/comment handling; Hisoka empirically confirmed an unbalanced brace returns nil -> require.NotEmpty fails loud, so blast radius is a loud test failure, not a shipped bug (Robin rated it P3 as a structural alternative: use hclsyntax, already imported; downgraded to Note per the empirical fail-safe bounding). No new P0/P1/P2. Event COMMENT + dismiss stale CHANGES_REQUESTED. Reviewed against 1142706..bc740d2.

Round 5

Churn guard: PROCEED. Author pushed 657abbb, 1cf64f1, 658b74b (PR rebased onto 5f3b875). All 7 R4 fixes (CRF-12/13/14/15/16/17/18) verified clean by the panel (12 reviewers) + Netero. CRF-10 contest closed in the author's favor (unanimous 8/8). CRF-6 (DOCS-610) and CRF-7 (DOCS-611) deferred/accepted with tickets; in-code note verified accurate; not re-raised. Panel (12): Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Gon, Leorio, ging-go, Komugi, Melody, Robin + wildcard Razor. New: CRF-20 (P3 language-selector drift guard, Bisky), CRF-23 (P3 CRF-13 behavioral residual untracked, Mafu-san), CRF-21/CRF-22/CRF-19 Notes, CRF-24 Nit. No new P0/P1/P2. Verified CRF-20 (6 param values vs 6 has_language branches, no binding test) in worktree. Event COMMENT + dismiss the stale R2/R4 CHANGES_REQUESTED (no blocking findings remain). Reviewed against 5f3b875..658b74b.

Round 4

Churn guard: PROCEED. Scope owner (@david-fraley) decided open q#1: move next to Docker, remove the IDE selector. Author implemented across b64ceea, 17186ff, 30fb31b: trimmed the base to infra + language selector + optional git-clone (removed ides/jetbrains params, all five IDE module blocks, presets), added a real collision guard (included_modules in base.json seeding validateModules, plus modules-endpoint filtering), changed ordering to group quickstart before docker (no first-position pin), and added the quickstart baseSpec row (CRF-11). All 5 open findings addressed by code. Panel to verify: CRF-4 (guard adequacy), CRF-6 (surviving git-clone registry hardcode). Significant restructure -> Gon + Leorio re-triggered. Reviewed against 8eaf4f5..30fb31b.
Panel (16): Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Gon, Leorio, ging-go, Komugi, Chopper, Melody, Meruem, Robin, Luffy + wildcards Zoro, Knov. Netero advisory: no findings. Verified fixed: CRF-1/2/4/5/8/9/11. CRF-4 collision guard works on both paths (compose reject + endpoint filter, tested), but the class is only half-closed -> CRF-12. New findings: CRF-6 re-raised P2 (6 reviewers; git-clone hardcode, BaseRenderContext has no RegistryBase, verified in worktree), CRF-12 P3 (8 reviewers; included_modules not enforced vs rendered blocks), CRF-13 P2 (README caching false promise), CRF-14 P3 (unanchored grep), CRF-15 P3 (vacuous test loop), CRF-16 P3 (comment duplication), CRF-17 Note (ConflictsWith half-pairing), CRF-18 Nit (Go modernization), CRF-10 re-raised P3 (install-languages.sh drift). CRF-3/CRF-7 acknowledged, no action. Event REQUEST_CHANGES (P2 present).

Round 3

Churn guard: BLOCKED. 1 addressed (CRF-9, ID tiebreak in 76aabd3, verified in worktree), 3 acknowledged (CRF-3/7/8), 4 contested and held pending open-question-#1 scope decision escalated to the scope owner (CRF-4/5/6/10, no ticket), 1 silent (CRF-11: no response, no code change; it was folded into the round-2 body with no thread). Per the verdict rules one silent finding blocks. No panel, no Netero. Posted BLOCKED status COMMENT (prior round-2 CHANGES_REQUESTED still stands). Reviewed against 8eaf4f5..76aabd3.

Round 2

Churn guard: PROCEED. CRF-1 and CRF-2 both addressed by commit 58eb2c9 (Sorted rewritten to assert quickstart-first + remaining alphabetical; positive assertion added). Panel proceeds.
Panel (12): Bisky, Hisoka, Mafu-san, Mafuuu, Pariston, Gon, Leorio, ging-go, Komugi, Robin, Luffy + wildcard Meruem. Netero re-ran (advisory once panel gates cleared): 1 new Note (CRF-3). Law did not re-run (effective additions grew only 7).
New findings: CRF-4 (P1 module collision, 6 reviewers convergent), CRF-5 (P2 JetBrains dead selector), CRF-6 (P2 registry hardcoded), CRF-10 (P3 verbatim-copy drift), CRF-7/8/9/11 Notes, CRF-3 Note. CRF-4/5/6 verified in orchestrator worktree (module blocks, commented-out jetbrains source, hardcoded registry, quickstart unique among bases). Process notes in body: CRF-2 clause-2 re-raise (Mafu-san P3, description still cites wrong test package; author acknowledged in reply), emdashes in PR body (Nit), README not byte-verbatim (prettier). Event REQUEST_CHANGES (P1 present). Reviewed against 8eaf4f5..58eb2c9.

Round 1

Netero-only first-pass gate. Law ran (effective additions 1049 > 1000): verdict Don't split, advisory. Netero found 1 P0, 1 Note. P0 verified by running go test ./coderd/ -run TestTemplateBuilderBases/Sorted in the orchestrator worktree (FAIL confirmed). Panel skipped per Netero decision gate (P0 present, pre-panel round). Posted Netero-only COMMENT review. Reviewed against 8eaf4f5..3a6ad9a.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First-pass review (Netero) only. The full reviewer panel has not yet reviewed this PR; it will run once the mechanical findings below are addressed. This is a clean, well-scoped scaffold: the base package is embedded through the existing //go:embed bases directive, the port from examples/templates/quickstart is faithful, and the open questions are correctly held inside one draft PR rather than spread across several. Law assessed the 1049-LOC headline and recommends against splitting: about 13 lines are novel logic and the rest is verbatim-ported template data plus a byte-identical golden duplicate.

Severity count: 1 P0, 1 Note.

Blocking issue: the quickstart-first ordering pin deterministically breaks the pre-existing TestTemplateBuilderBases/Sorted test in package coderd. Verified by running the test in a clean worktree. The PR introduces a new ordering contract (quickstart first, rest alphabetical) but leaves the test asserting the old contract (strictly alphabetical across all bases).

Note (process): the PR description reports go test ./coderd/templatebuilder/ passing, but the failing Sorted assertion lives in package coderd (coderd/templatebuilder_handler_test.go), which that command never exercised. Running the package that contains the handler test would have surfaced this. There is also no positive test asserting the new quickstart-first ordering; add one alongside fixing Sorted.

For the panel's next round, Law flagged that testdata/quickstart.tf.golden is byte-for-byte identical to bases/quickstart/main.tf.tmpl because the template carries no Go-template directives, so as Law put it, the golden test asserts only "the file we embedded equals the file we embedded." Weak but not vacuous; the panel will take a closer look after the P0 is fixed.

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/templatebuilder_handler.go Outdated
@nickvigilante

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Full panel review (round 2). The prior-round fixes are verified closed: the Sorted test now asserts resp.Bases[0].ID == "quickstart" and checks the remainder is name-sorted from index 2, which addresses the root cause of CRF-1, and the CRF-2 positive assertion is present. The ordering pin itself is clean and minimal, the port is faithful, and the open questions are disclosed honestly in a draft marked do-not-merge. Law's verdict stands: don't split.

Severity count: 1 P1, 2 P2, 1 P3, and several Notes/Nits.

The central issue is that the panel converged, from five independent angles, on one root cause: this is a fully-composed, batteries-included template shipped in the base slot, and the builder's model is base (infra) + modules composed on top. As Luffy put it, "It's not a base. It's a whole finished template wearing a base costume." That mismatch produces the P1 collision (CRF-4), the P2 registry hardcoding (CRF-6), the P2 dead JetBrains selector (CRF-5), and the P3 verbatim-copy drift (CRF-10). Open question #1, option (a) (strip modules/presets so the base is pure infra) dissolves all four at once. Verified in a worktree: quickstart is the only base that embeds catalog-named module blocks; the siblings declare at most azure_region/gcp_region, which are not catalog IDs, so this hazard is introduced by this PR, not pre-existing.

Independently of the #1 decision, the compose pipeline has no guard for base-declared module names colliding with wizard-selected ones. validateModules only dedupes the requested list. Even if this base is trimmed, the next base that legitimately bundles a catalog module hits the same wall. Pariston and Meruem propose the durable fix: declare the base's included module IDs (e.g. included_modules in base.json) and seed validateModules' seen-map from it, so the disjoint-namespace invariant is enforced by the same mechanism that already rejects duplicate requests.

The draft status and the open-questions block do not close these findings. There is no ticket for #1, #2, or #3, and a paragraph in the description is not a resolution. Each needs a human decision: fix it here, file a tracking issue, or explicitly accept the gap.

Process notes (not blocking): the Testing section still lists only go test ./coderd/templatebuilder/, which cannot exercise the ordering logic or its new assertion (both live in package coderd); the author acknowledged this in a reply but the PR body is unchanged, so update it to cite go test ./coderd/. The PR description uses emdashes throughout; the shipped files are clean so make lint/emdash will not fail, but the body violates the house standard. Minor accuracy: the README is not byte-verbatim (prettier reformatted the table separators), so "verbatim + markers" slightly overstates it.


coderd/templatebuilder_handler_test.go:44

Note [CRF-11] Quickstart is absent from the handler-level specs table, so its API surface is only checked by generic loops (Bisky).

TestTemplateBuilderBases/OK asserts name, icon, OS, and variable presence for a curated specs list; quickstart is not in it, so the handler never asserts which variables it exposes (currently none). Given open question #3 (whether Quickstart should expose a container_image selector), an explicit spec row would lock the current answer and flag drift. Worth knowing, not blocking.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/templatebuilder/bases/quickstart/main.tf.tmpl Outdated
Comment thread coderd/templatebuilder/bases/quickstart/main.tf.tmpl Outdated
Comment thread coderd/templatebuilder/bases/quickstart/main.tf.tmpl Outdated
Comment thread coderd/templatebuilder/bases/quickstart/main.tf.tmpl
Comment thread coderd/templatebuilder/bases/quickstart/base.json
Comment thread coderd/templatebuilder_handler.go Outdated
Comment thread coderd/templatebuilder_handler.go Outdated
Comment thread coderd/templatebuilder/testdata/quickstart.tf.golden
@nickvigilante

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Round 3 status: review is on hold. The churn guard classified the prior findings and the panel did not re-run this round, because the code is essentially unchanged (only CRF-9 landed) and the substantive findings are waiting on a decision that has not happened yet.

What moved:

  • CRF-9 (unstable/non-total sort): fixed in 76aabd3. Confirmed in a worktree: the comparator now tiebreaks on ID (if bases[i].Name != bases[j].Name { ... } return bases[i].ID < bases[j].ID), so the order is total and deterministic even if two bases share a display name. Closed. Switching to slices.SortFunc + cmp.Compare remains optional.

Held pending the open-question-#1 scope decision (escalated to the scope owner):

  • CRF-4 (P1, module collision), CRF-5 (P2, dead JetBrains selector), CRF-6 (P2, hardcoded registry), CRF-10 (P3, verbatim-copy drift). You confirmed these share one root and committed to a concrete fix under either option (a) strip to infra or (b) collision guard. That is the right call, and not shipping a first-position base that breaks on the wizard's core action is correct. These stay open findings, not resolved, until the chosen option is implemented or a tracking ticket is filed. A human decision is what unblocks them.

Acknowledged with reasoning, no action expected now:

  • CRF-7 (container image, open q#3), CRF-8 (ID pin in comparator, open q#2), CRF-3 (golden equals source).

One finding is unaddressed with no response and no code change:

  • CRF-11 (Note): quickstart is absent from the handler-level specs table in coderd/templatebuilder_handler_test.go, so its API surface (currently no variables) is only checked by generic loops. It was folded into the round-2 review body, so there is no thread to reply on. Either add a specs row for quickstart or say why it should not be locked. This is minor, but it is the one item with no engagement.

The round-2 CHANGES_REQUESTED still stands. The panel will re-review once you push the open-question-#1 implementation (or file a ticket for it) and address or respond to CRF-11. Re-request review then.

🤖 This review was automatically generated with Coder Agents.

@nickvigilante

Copy link
Copy Markdown
Contributor Author

Re CRF-11 (quickstart absent from the handler specs table): addressed in b64ceea.

Added a quickstart row to the baseSpec table in TestTemplateBuilderBases/OK asserting its OS and that it currently exposes no builder variables (hasVariables: false), matching the existing aws-linux precedent. Builder variables come from base.json's variables array, which quickstart doesn't declare, so this locks the current answer and flags drift if open question #3 later adds a container_image selector (the row's expectedVars would be updated alongside that base.json change).

This was the one finding folded into the round-2 body with no inline thread, hence this top-level reply rather than a thread response.

@nickvigilante
nickvigilante marked this pull request as ready for review July 15, 2026 18:31
@coderagents

coderagents Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

The template builder is enabled by default and its base-selection and module-selection steps are documented in docs/admin/templates/creating-templates.md. This PR adds a new Coder Quickstart base and refines module filtering. It is now marked ready for review (no longer a draft). Quickstart is grouped immediately before the Docker base (it's a Docker-based starter), not pinned first.

Updates Needed

  • docs/admin/templates/creating-templates.md - note the Quickstart base pinned first as the recommended starting point (no longer applicable: 17186ff changed the ordering so Quickstart is grouped next to Docker rather than pinned first as "recommended"; the base-selection step describes bases with an illustrative examples list and does not enumerate them, so adding one more base does not require a prose change)
  • docs/admin/templates/creating-templates.md - The "Select modules" step says modules are "filtered for compatibility with the selected base template's operating system." As of 98a342e/3ccaf2c, modules a base already bundles (its included_modules, e.g. git-clone for Quickstart) are also hidden from that step, and selecting one is rejected at compose time. Update the filtering description so it matches the current behavior.
  • docs/images/templatebuilder_01_bases.png - The base-selection screenshot predates this change and does not show the new Coder Quickstart base (positioned just before Docker). Refresh it so the screenshot matches the base list in the UI.

Note: the base's own README.md (coderd/templatebuilder/bases/quickstart/README.md) is embedded template content surfaced in the builder, not published under docs/, so it isn't part of the docs site.


Automated review via Coder Agents

@david-fraley david-fraley left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We chatted, follow ups for you -- move it to be next to the Docker template and also remove the IDE selector

@nickvigilante

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Full panel review (round 4). The scope decision landed and was implemented well. The base is trimmed to infra plus a language selector and optional git-clone, the dead IDE selector is gone (CRF-5), the ordering is a clean group-before-Docker with a total-order comparator (CRF-8/CRF-9), the baseSpec row is in (CRF-11), and the collision guard is real: it rejects a base-included module in compose and hides it from the modules endpoint, with tests on both paths. Verified in a worktree. CRF-4's git-clone instance is genuinely fixed. As Robin put it, "You found the seam and cut it. But the poneglyph next to it still has an exact twin."

Severity count: 2 P2, 5 P3, 1 Note, 1 Nit. Two prior findings re-raised (CRF-6, CRF-10).

The two P2s both trace to one decision: keeping a catalog module (git-clone) embedded in the base.

  • CRF-6 (re-raised, six reviewers): the base's git-clone still hardcodes registry.coder.com, and this is structural, not a stray literal. BaseRenderContext has no RegistryBase field and renderBase is never passed the deployment RegistryURL, so a base cannot honor CODER_TEMPLATE_BUILDER_REGISTRY_URL the way wizard-composed modules do. The same git-clone module resolves through the mirror when the wizard renders it but reaches the public registry when the base embeds it, on the same deployment. Fair context from Hisoka and Knov: this base is unusable fully air-gapped regardless (it pulls the enterprise image, apt, nodesource, go.dev, rustup), so the real value here is mirror consistency on internet-connected deployments, and three shipped bases already hardcode region-module sources, so this is a base-layer class. It still needs a human decision (no ticket): thread RegistryBase into base rendering, drop git-clone from the base and let the wizard provide it (which also removes the guard's need for git-clone, per Knov/Melody/Pariston), or track it.
  • CRF-13 (two reviewers): the README promises languages are "installed on first start and cached for subsequent starts," but the installs land in the ephemeral container (only /home/coder persists), so they reinstall from the network on every start, and start_blocks_login = true holds the user's login during that reinstall. On mirror/air-gapped deployments the reinstall fails and login never unblocks. Fix the doc to match the behavior, or persist the toolchains.

The collision guard works but its invariant is not enforced (CRF-12, eight reviewers): included_modules is a hand-maintained copy of the base's actual module blocks with nothing binding them. Add a module "code-server" block to any base and forget the JSON line, and CRF-4's P1 duplicate-module break returns silently. The fix addressed the git-clone instance; the durable fix is mechanical, a test that parses each base's rendered module "<id>" labels and asserts they equal that base's included_modules (or derive the set from the template and delete the field). AGENTS.md prefers mechanical enforcement over a remembered rule, and this guard exists to prevent a P1.

Smaller items: CRF-10 re-raised, the drift class survived the trim in install-languages.sh.tftpl (still byte-identical to the example); CRF-14 unanchored grep language dispatch (safe today, latent); CRF-15 a vacuous negative test loop; CRF-16 the collision rationale is duplicated ~6 times across the diff (also at bases.go:268); CRF-17 the "bidirectional" conflict comment overpromises for base-included modules; CRF-18 stdlib modernization for the comparator/regroup. No action needed on CRF-3 (the directive-free golden still asserts source==source; all reviewers concur it is low-value but not vacuous). CRF-7 (container image) remains deferred; the scope owner did not require it, but there is no ticket, so it needs an explicit accept-or-track. Luffy also noted two of the four presets ("Backend (Go)", "Data Science") select a single language the dropdown already offers.


coderd/templatebuilder/compose.go:236

Note [CRF-17] The "Check conflicts bidirectionally" loop iterates only requested modules, so a base-included module's own ConflictsWith is never enforced (Mafuuu, Melody).

Base module IDs are seeded into seen, so a requested module that declares a conflict with a base module is caught, but the base module's own ConflictsWith list is never consulted. If a base ever includes module A where A declares a conflict with B but B does not reciprocate, selecting B composes cleanly. Latent today (git-clone's conflicts_with is empty and catalog conflicts appear symmetric); flagged because the comment claims a bidirectionality that does not extend to base-included modules.

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/templatebuilder/bases/quickstart/base.json Outdated
Comment thread coderd/templatebuilder/bases/quickstart/README.md Outdated
Comment thread coderd/templatebuilder/bases/quickstart/install-languages.sh.tftpl Outdated
Comment thread coderd/templatebuilder_handler_test.go
Comment thread coderd/templatebuilder_handler.go Outdated
Comment thread coderd/templatebuilder_handler.go Outdated
@github-actions github-actions Bot added the stale This issue is like stale bread. label Aug 1, 2026
@github-actions github-actions Bot closed this Aug 4, 2026
@nickvigilante nickvigilante reopened this Aug 4, 2026
@nickvigilante
nickvigilante marked this pull request as draft August 4, 2026 16:01
@nickvigilante
nickvigilante force-pushed the vigilante/docs-558-add-coder-quickstart-template-to-template-builder branch from 30fb31b to 658b74b Compare August 4, 2026 18:01
@nickvigilante

Copy link
Copy Markdown
Contributor Author

R4 findings are answered in each thread and pushed as 657abbb, 1cf64f1, 658b74b.

One finding had no inline thread (it was folded into the review body):

CRF-17 (conflict-check comment): fixed in 1cf64f1. The comment now states that base-included modules seed the seen-set (so a requested module conflicting with one is caught), but a base module's own ConflictsWith list is not consulted, which is safe because base modules are curated.

For the round: CRF-12/15 (657abbb), CRF-13/14 and the CRF-6 note (658b74b), and CRF-16/17/18 (1cf64f1) are all code changes; CRF-6 and CRF-7 are accept-and-track (DOCS-610 with a stacked follow-up PR, and DOCS-611); CRF-10 is a push-back (details in-thread).

Validation on the touched packages: go test ./coderd/ ./coderd/templatebuilder/ including -race -shuffle=on, plus gofmt, go vet, terraform fmt, typos, and make lint/emdash, all clean.

@nickvigilante

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Full panel review (round 5), a verification round. This is in good shape: all seven R4 fixes hold up under the panel's checks, and I'm dismissing the prior CHANGES_REQUESTED because no blocking (P0-P2) findings remain.

Verified fixed: CRF-12 (the invariant test TestBaseIncludedModulesMatchRendered renders each base, extracts module "<id>" labels via ExtractModuleNames, and asserts they equal included_modules, so the manifest can no longer silently drift, this closes the class, not the instance), CRF-13 (README no longer promises caching), CRF-14 (has_language whole-entry match), CRF-15 (require.NotEmpty), CRF-16 (comment dedup), CRF-17 (honest ConflictsWith comment), CRF-18 (slices.SortFunc/IndexFunc/Insert). As Bisky put it, "Costume jewelry does not survive this cut."

Contested finding closed in the author's favor: CRF-10 (base install-languages.sh drift). Eight reviewers independently diffed the base against the example and confirmed the base has genuinely diverged (base uses has_language, the example still uses grep -q), so a base==example test would wrongly block legitimate divergence, and the real manifest-vs-rendered drift is now covered by CRF-12. Defense valid.

Deferred/accepted with tickets, not re-raised: CRF-6 (git-clone registry, DOCS-610 with a stacked follow-up PR) and CRF-7 (container image, DOCS-611). The in-code CRF-6 note was verified accurate.

Remaining items are all P3 or below, none blocking:

  • CRF-20 (P3): the language selector's option values and the install script's has_language branches are the same kind of hand-maintained two-list pairing you just made mechanical for included_modules, but with no cross-check test. Verified: six values, six branches, they agree today, nothing binds them. Drift silently no-ops a selected language (the CRF-5 shape). The same ExtractModuleNames-style test would close it.
  • CRF-23 (P3): CRF-13's doc fix is honest, but the behavior it now documents (every start reinstalls toolchains from the network and blocks login) is a real limitation on network-restricted deployments with no ticket. DOCS-610 covers the git-clone registry, not this. Per the no-follow-up rule, confirm it is tracked or explicitly accepted.
  • CRF-21 (Note): the CRF-12 invariant test renders with DefaultBaseRenderContext only, but production renderBase overlays merged variables, so a base that gated a catalog module block on a variable would evade the guard. Dormant today (quickstart declares no variables), worth a comment or a variable-context case if bases ever gain variables.
  • CRF-22 (Note): the README's leading "reinstalled on every start" is not literally true for Rust ($HOME/.cargo persists); the trailing "most" hedges it, and Leorio (docs) considers the current wording acceptable. Optional refinement.
  • CRF-24 (Nit): the sort-site comment still repeats the groupQuickstartBeforeDocker rationale that the helper's own doc carries (a residual of the CRF-16 dedup).

Out of this PR's scope but worth a follow-up: the source example examples/templates/quickstart/install-languages.sh.tftpl still carries the old unanchored-grep substring dispatch that CRF-14 fixed in the base (flagged by Mafu-san, Hisoka, Razor). Port has_language to the example in its own change. ging-go also noted the adjacency loop in the Sorted test could be slices.IsSorted (loses the per-pair failure message, so optional).

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/templatebuilder/bases/quickstart/main.tf.tmpl
Comment thread coderd/templatebuilder/bases/quickstart/main.tf.tmpl
Comment thread coderd/templatebuilder/compose_test.go Outdated
Comment thread coderd/templatebuilder/bases/quickstart/README.md Outdated
Comment thread coderd/templatebuilder_handler.go Outdated
@nickvigilante
nickvigilante force-pushed the vigilante/docs-558-add-coder-quickstart-template-to-template-builder branch from 658b74b to bc740d2 Compare August 4, 2026 19:31

Copy link
Copy Markdown
Contributor Author

R5 findings addressed. All code changes are in bc740d2 (the branch was rebased onto main at push, so the per-thread commit hashes from the last round now live under new SHAs; the changes they referenced are unchanged).

CRF-20 (P3) — fixed in bc740d2. Added ExtractParameterOptionValues and TestQuickstartLanguageSelectorMatchesInstallScript: it renders the base, extracts the languages selector's option values and the script's has_language dispatch names, and asserts the two sets match. Same shape as the included_modules invariant, so these two hand-maintained lists can no longer drift silently. Verified it fails on drift (renaming the script's cpp branch to cxx trips ElementsMatch) and passes again on revert.

CRF-21 (Note) — addressed in bc740d2. Documented on TestBaseIncludedModulesMatchRendered that it renders with DefaultBaseRenderContext (no variable overlay), so a base that gated a catalog module block on a variable could evade the guard, and that the test should gain a variable-context case when bases start declaring variables. Left as a comment rather than a new case because no base declares variables today (dormant), matching your framing.

CRF-22 (Note) — fixed in bc740d2. The README now names Rust as the explicit exception (its ~/.cargo toolchain persists on the home volume and is detected and reused) instead of a blanket "reinstalled on every start," while keeping the point that the other toolchains reinstall from the network each start.

CRF-23 (P3) — tracked in DOCS-614 (Medium). CRF-13's doc fix stands; the underlying limitation (language toolchains reinstall from the network every start, block login, and fail on network-restricted/air-gapped deployments) is now a ticket with possible directions (persist to the home volume, a prebuilt image / prebuilds, or a non-blocking install). Distinct from DOCS-610, which covers the git-clone module registry.

CRF-24 (Nit) — fixed in bc740d2. Dropped the duplicated grouping rationale at the sort site; it's now a short pointer to groupQuickstartBeforeDocker, whose own doc carries the "Docker-based starter, grouped next to Docker" reasoning.

Out of scope

  • The source example examples/templates/quickstart/install-languages.sh.tftpl still carries the pre-CRF-14 unanchored grep dispatch. Tracked in DOCS-613 to port has_language there in its own change, as suggested.
  • slices.IsSorted for the Sorted adjacency loop: keeping the explicit loop so a failure names the specific out-of-order pair, which IsSorted would drop. Declining on that basis.

Validation on the touched packages: go test ./coderd/ ./coderd/templatebuilder/ including -race -shuffle=on, plus gofmt, go vet, and the typos/emdash checks, all clean.

@nickvigilante

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Full panel review (round 6), verification. All four R5 fixes hold, no blocking findings remain, and I'm dismissing the prior CHANGES_REQUESTED.

Verified closed: CRF-20 (the language-selector-vs-has_language drift guard is real, six reviewers independently mutation-tested it, renaming a script branch or a selector option fails the test loudly), CRF-21 (the default-context caveat on the invariant test is accurate), CRF-22 (the README now names Rust as the persist-and-reuse exception, matching the $HOME/.cargo guard), CRF-24 (the sort-site comment is a one-line pointer). Deferrals with tickets confirmed and not re-raised: CRF-6 (DOCS-610), CRF-7 (DOCS-611), CRF-23 (DOCS-614); the out-of-scope example-grep is tracked (DOCS-613). As Hisoka put it: "Boring, in the way a well-built thing is boring."

One new P3 and one Note, neither blocking:

  • CRF-25 (P3): the same drift class the panel closed with CRF-5 and CRF-20 has a third consumer the new guard does not cover. The four coder_workspace_preset blocks declare languages a third time (web_dev, backend_go, data_science, full_stack), and nothing binds them to the selector. Today all preset values are valid, so it is latent, exactly as CRF-20 was when rated P3. Extend TestQuickstartLanguageSelectorMatchesInstallScript to also assert each preset's decoded languages is a subset of the selector options.
  • CRF-19 (Note): ExtractParameterOptionValues and ExtractModuleNames hand-roll HCL parsing (a brace-depth scan that counts braces inside strings/heredocs, regexes that don't skip comments) with no direct edge-case test. Hisoka landed the probe rather than reasoning about it: an unbalanced { makes the extractor return nil, which trips require.NotEmpty and fails the test loudly, and these functions are test-only, so the blast radius is a red test on the developer who edits the base, not a mis-installed language in a user's workspace. It fails toward safety, which is why it stays a Note. Robin proposes the structural fix (use hashicorp/hcl/v2 hclsyntax, already a dependency and already used in tfparse.go to scope to a named coder_parameter block), which deletes the hand-rolled scanner and its assumption; a table test over crafted HCL is the lighter alternative. Zoro's counter is fair: a regex cannot balance braces, so the manual scan is the only regex-based option, but that is an argument for the parser, not for the scan.

The collision guard, the two drift invariants, the total-order sort, and the trimmed base all hold across the panel. Nothing here blocks merge.

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/templatebuilder/bases/quickstart/main.tf.tmpl
Comment thread coderd/templatebuilder/render.go Outdated
@github-actions github-actions Bot removed the stale This issue is like stale bread. label Aug 5, 2026
@github-actions github-actions Bot added the stale This issue is like stale bread. label Aug 18, 2026
Comment thread coderd/templatebuilder_handler.go Outdated
Comment thread coderd/templatebuilder/bases.go Outdated
Comment thread coderd/templatebuilder_handler_test.go Outdated

@jeremyruppel jeremyruppel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking design suggestion on the base/module collision guard. The current approach keeps two lists in sync (the included_modules manifest field and the modules the base's Terraform actually renders) and adds an invariant test to police the drift. Since the base already declares the modules it bundles by literally rendering them, and Compose already renders the base before validating modules, we could derive the included set from the rendered HCL via the new ExtractModuleNames helper instead of maintaining a parallel manifest field. That removes the concept, the field, and the sync test, and promotes ExtractModuleNames from a test-only helper to a real production caller. Left inline notes on the specific lines. Details below.

Sketch of the derive-from-render approach
// bases.go — replace the manifest field + lookup with a derived, cached set.
var baseIncludedModules = sync.OnceValues(func() (map[string][]string, error) {
    bases, err := loadBases()
    if err != nil {
        return nil, err
    }
    out := make(map[string][]string, len(bases))
    for id := range bases {
        mainTF, err := RenderBaseTemplate(id, "main.tf.tmpl", DefaultBaseRenderContext(id))
        if err != nil {
            return nil, xerrors.Errorf("render base %q: %w", id, err)
        }
        out[id] = ExtractModuleNames(mainTF)
    }
    return out, nil
})

func BaseIncludedModules(exampleID string) []string {
    all, err := baseIncludedModules()
    if err != nil {
        return nil
    }
    return all[exampleID]
}
  • compose.go and templatebuilder_handler.go call sites are unchanged (they already call BaseIncludedModules).
  • Net: deletes the included_modules JSON field, the IncludedModules struct field, and the sync test; adds ~15 lines of derive-and-cache.
  • Two caveats to carry over: ExtractModuleNames returns all module labels (not just catalog IDs), which is harmless here; and the derivation renders under DefaultBaseRenderContext, so a module block gated behind a base variable wouldn't be detected (no base does this today — same limitation the sync test documents, just relocated).

This is a suggestion, not a blocker — happy to discuss if you'd rather keep the explicit manifest field for forward-compat toward preconfigured/base-configured modules.

Generated by Coder Agents on behalf of @jeremyruppel.

Comment thread coderd/templatebuilder/bases.go Outdated
Comment thread coderd/templatebuilder/bases.go Outdated
Comment thread coderd/templatebuilder/bases/quickstart/base.json Outdated
Comment thread coderd/templatebuilder/compose_test.go Outdated

@jeremyruppel jeremyruppel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Follow-up on the extraction helpers and test scope. The selector/script drift this test guards is genuinely worth catching (it's silent and cross-language), but it can be done with a small regex mirroring the existing has_language one, without the HCL extractor family. Bundling piecemeal HCL parsing into this base PR is what creates the test-only exported surface. Suggest keeping regex here and landing the HCL migration as its own focused effort. Inline notes below.

Generated by Coder Agents on behalf of @jeremyruppel.

Comment thread coderd/templatebuilder/compose_test.go
Comment thread coderd/templatebuilder/render.go Outdated
Comment thread coderd/templatebuilder/render.go Outdated
Port the existing `quickstart` example template into the template builder's
base catalog so it can be selected in the guided wizard, and surface it first
in the base list as the recommended starting point.

- Add coderd/templatebuilder/bases/quickstart (base.json, README with
  prerequisite markers, main.tf.tmpl, install-languages.sh.tftpl), ported
  as-is from examples/templates/quickstart.
- Pin the quickstart base first in the bases endpoint; other bases stay
  alphabetical.
- Extend base ID/OS/snapshot tests and add the rendered golden file.

Scaffold for DOCS-558. First-position placement and the template's
scope/contents are still pending sign-off; see the PR description.
The quickstart-first pin changed the bases ordering contract, which broke
the existing TestTemplateBuilderBases/Sorted assertion (strict alphabetical
across all bases). Update Sorted to encode the new contract: quickstart is
pinned first, and the remaining bases stay sorted by name.

Addresses coder-agents-review CRF-1 (P0) and the missing positive-ordering
coverage noted in CRF-2.
Tiebreak the bases comparator on ID when display names are equal, so the
sort is total and deterministic regardless of sort.Slice's instability.

Addresses coder-agents-review CRF-9.
Quickstart was absent from the curated baseSpec table in
TestTemplateBuilderBases/OK, so its API surface (name, icon, OS, and
variable presence) was only exercised by the generic response loops.
Add an explicit row asserting it exposes no builder variables today,
locking the current answer so drift (e.g. later exposing a
container_image selector) is flagged.
Addresses review feedback on the Coder Quickstart base:

- Remove the IDE selector: drop the ides parameter, the dependent
  jetbrains_ides parameter, the IDE locals, and the IDE modules
  (code-server, cursor, jetbrains, zed, windsurf); strip the ides/
  jetbrains_ides keys from the presets; and update the README, which no
  longer advertises editor selection. Editors are added via the builder's
  module step instead. This also removes the dead JetBrains selector and
  the IDE base/module name collisions.
- Placement: sort bases alphabetically by name and group the quickstart
  base immediately before the Docker base instead of pinning it first.

Regenerate the quickstart golden and update the Sorted test for the new
ordering.
Keep the quickstart base's git-clone module (build-time repo cloning is a
useful quickstart affordance) but stop it from colliding with the wizard's
own Git Clone module.

- Add an optional included_modules field to base.json (BaseManifest)
  listing the catalog module IDs a base already declares in its own
  Terraform. Quickstart declares ["git-clone"].
- Seed validateModules' seen-set with those IDs so a wizard-selected
  module the base already renders is rejected with a clear error,
  enforcing a disjoint base/module namespace for every base.
- Filter GET /templatebuilder/modules?base=<id> to omit the base's
  included modules, so the wizard never offers a colliding module.

Tests: compose rejects quickstart + git-clone and still allows a
non-included module; the modules endpoint excludes git-clone for
quickstart while keeping it for docker.
Add ExtractModuleNames and a per-base test asserting each base's
included_modules manifest field exactly lists the catalog modules it
renders. Without this, adding a `module "<catalog-id>"` block to a base
and forgetting the manifest line silently re-opens the duplicate-module
collision the guard exists to prevent.

Also assert the quickstart modules endpoint returns a non-empty list, so
the git-clone exclusion test can no longer pass vacuously.

Addresses CRF-12 and CRF-15.
Replace the hand-rolled base sort and quickstart/docker regroup with
slices.SortFunc + cmp.Or and slices.IndexFunc/Insert. Behavior is
unchanged (verified by TestTemplateBuilderBases).

Consolidate the base/module collision rationale onto
BaseManifest.IncludedModules and point the other sites at it instead of
re-explaining it. Correct the conflict-check comment: base-included
modules seed the seen-set, but a base module's own ConflictsWith is not
consulted.

Addresses CRF-16, CRF-17, and CRF-18.
Correct the README claim that languages are cached across starts: most
tools install into the ephemeral workspace container, so they reinstall
from the network on every start and block login until done.

Match selected languages against whole comma-separated entries instead
of an unanchored grep substring, so a future value can never partially
match another.

Note in the base that its git-clone module renders the public registry
verbatim and does not yet honor a deployment's registry mirror; threading
the registry through base rendering is tracked in DOCS-610 as a stacked
follow-up. Regenerate the quickstart golden for the added comment.

Addresses CRF-13, CRF-14, and CRF-6.
- CRF-20: bind the quickstart language selector to the install script.
  Add ExtractParameterOptionValues and a test asserting the selector's
  option values match the script's has_language branches, so the two
  hand-maintained lists cannot drift silently.
- CRF-21: document that TestBaseIncludedModulesMatchRendered renders with
  DefaultBaseRenderContext (no variable overlay) and to extend it if bases
  gain variables.
- CRF-22: make the README precise that Rust's ~/.cargo persists while other
  toolchains reinstall each start.
- CRF-24: drop the duplicated grouping rationale at the sort site (kept on
  groupQuickstartBeforeDocker).
- CRF-19: replace the hand-rolled HCL parsing in the template-builder base
  extractors with hclsyntax (matching tfparse.go). A shared parseHCLBody backs
  ExtractModuleNames and ExtractParameterOptionValues, so braces inside strings
  or heredocs and commented-out blocks no longer confuse extraction.
- CRF-25: add ExtractPresetParameterValues and extend
  TestQuickstartLanguageSelectorMatchesInstallScript to assert each
  coder_workspace_preset's languages are a subset of the selector options,
  closing the third hand-maintained language list.
- Add TestExtractHCLHelpers covering the edge cases (braces in a string, a
  commented-out option block, and jsonencode unwrapping).
- CRF-27: port ExtractAgentResourceName off the regex/brace-scan onto the shared hclsyntax parseHCLBody, so count/for_each is detected wherever it sits in the agent block, not only before the first closing brace. It was the last extractor on the old scanner and the only one on the production composition path. Pre-existing (#26901); ported here since the rewrite established hclsyntax as the in-package parser. Adds a CountAfterNestedBlock regression test.

- CRF-28: add ExtractPresetNames and assert len(presetLangs) == len(presetNames), so a preset that set languages to a non-literal would drop out and fail the guard instead of silently evading the subset check.

- CRF-29: drop the review-artifact comment prefix from compose_test.go.

- CRF-30: rewrite the TestExtractHCLHelpers doc to describe the input under test rather than the removed implementation.
- ExtractAgentResourceName: describe behavior directly instead of narrating
  the removed regex implementation.
- parseHCLBody: note that its one production caller turns a nil body into an
  error, alongside the test-only extractors that fail their assertions.
- ExtractPresetParameterValues: match ExtractPresetNames by requiring exactly
  two labels on a coder_workspace_preset block, so both halves of the preset
  count guard use the same predicate.

No behavior change; a coder_workspace_preset data block always carries two
labels.

DOCS-558
… to client

Addresses @jeremyruppel's review of the quickstart base.

- Derive a base's included catalog modules from its rendered Terraform
  (cached render + ExtractModuleNames, filtered to catalog IDs) and delete the
  base.json `included_modules` field, the `IncludedModules` manifest field, and
  TestBaseIncludedModulesMatchRendered. The base Terraform is now the single
  source of truth; a new TestBaseIncludedModules covers the derived path.
- Move the Quickstart-before-Docker placement off the server (delete
  groupQuickstartBeforeDocker and its position assertion) and onto the client
  base-infra select step, mirroring ModuleSelectStep's priority sort via a
  shared sortByPriority helper. The bases endpoint returns a plain, name-sorted
  (ID-tiebroken) list.
- Revert ExtractAgentResourceName/ExtractModuleNames to the pre-hclsyntax regex
  extraction and drop the option/preset HCL extractor family, keeping this PR
  scoped to "add quickstart base". The selector<->install-script drift test
  uses a small regex instead.
@nickvigilante
nickvigilante force-pushed the vigilante/docs-558-add-coder-quickstart-template-to-template-builder branch from 3ccaf2c to 586c53b Compare August 26, 2026 15:26
Comment thread site/src/pages/TemplateBuilder/sortByPriority.ts
Add vitest unit tests for the client-side base-ordering helper: priority
ordering, stability among unlisted items, missing priority ids, input
immutability, and the empty-array case.
@nickvigilante nickvigilante changed the title feat(coderd): add Coder Quickstart base to the template builder feat: add Coder Quickstart base to the template builder Aug 26, 2026

@jeremyruppel jeremyruppel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks great! this is an awesome addition to template builder ❤️

@nickvigilante
nickvigilante dismissed david-fraley’s stale review August 27, 2026 00:29

Got Jeremy's passing review, and I implemented David's feedback already

@nickvigilante
nickvigilante merged commit c78dca5 into main Aug 27, 2026
31 of 32 checks passed
@nickvigilante
nickvigilante deleted the vigilante/docs-558-add-coder-quickstart-template-to-template-builder branch August 27, 2026 00:29
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants