Skip to content

ADR-021: Represent Project Type on PresentationModel as One Display String, Not a Boolean Set

Field Value
Date 2026-08-06
Status Accepted
Deciders Holger Zahnleiter
Supersedes
Superseded by

Context and Problem Statement

repo init's SUMMARY output shows the generated project's language but not its project type (library/service/tool/react-frontend/firmware-stm32) — an inconsistency, since both are equally fundamental facts about what got generated. internal.ProjectDescriptor.ProjectType already carries this value, but model.NewPresentationModelForTemplatingGenerator reads it only once, to derive the single IsService boolean, then discards it — PresentationModel has no field a caller can use to display it.

PresentationModel already has a precedent for representing language this way: four booleans (IsCppProject/IsGoProject/IsJavaProject/IsTypescriptProject), because template-selection code throughout internal/templating/{cpp,go,java,typescript} and the .tmpl files themselves branch on language repeatedly. Should project type follow the same boolean-per-value pattern?

Decision Drivers

  • library_design.md: "Minimize public API surface; expose only what consumers genuinely need."
  • Every current place that branches on project type — capability/generator.go's profile lookup, each language's capabilities.go (goTypeDir, javaTypeDir, cppTypeDir, Makefile-partial selection), adrs.Compute — reads internal.ProjectType directly off ProjectDescriptor, never through PresentationModel. Unlike language, PresentationModel currently has zero consumers of project-type information.
  • SUMMARY is the only thing that will read this field once added.

Considered Options

  1. Add one derived field, ProjectType string, populated via project.ProjectType.String().
  2. Add a parallel five-boolean set (IsLibrary/IsService*/IsCLITool/IsReactFrontend/ IsFirmware) mirroring the language pattern (*IsService already exists, reused).
  3. Don't touch PresentationModel at all; derive the display string inline at print time from internal.ProjectType, passed as an additional parameter to BuildPostGenerationSummary.

Decision Outcome

Option 1.

ProjectType string // e.g. "library", "service", "command line tool" (see internal.ProjectType.String())

populated in NewPresentationModelForTemplatingGenerator via project.ProjectType.String().

Positive Consequences

  • Matches how every other print-ready SUMMARY value already lives on PresentationModel (ProjectName, TechnicalArtifactName, ...) — one consistent way of threading data into BuildPostGenerationSummary, not two.
  • No unused surface: the field has exactly the one consumer that motivated adding it.
  • Verified directly by TestNewPresentationModelForTemplatingGenerator_ProjectType (internal/templating/model/presentation_model_test.go), a table-driven test over all five internal.ProjectType values — proving the single field correctly covers every case a boolean-per-value scheme would otherwise have needed to cover individually.

Negative Consequences / Risks

  • If a future template genuinely needs to branch on project type the way .tmpl files branch on language today, this field alone won't support that (a string isn't a template-friendly conditional the way a bool is) — at that point, per-type booleans would need to be added alongside it. No such need exists today; adding them now would be speculative.

Rejected alternatives

  • Option 2 (five booleans) would add public struct surface with no current caller — exactly the unused-API-surface library_design.md warns against. The language booleans earn their keep because template code actually branches on them; project type has no such caller yet.
  • Option 3 (no model field, pass internal.ProjectType straight through) was rejected because it introduces a second, inconsistent way of getting data into BuildPostGenerationSummary — every other SUMMARY value already flows through PresentationModel, and internal.ProjectType is a domain type from the internal package rather than a display-ready string, so internal/templating would gain a direct dependency on internal.ProjectType's String() method at the call site instead of the model owning that translation once.
  • internal/project_type.goProjectType.String(), the source of the display text (its PROJECT_TYPE_TOOL case was corrected from "comand line tool" to "command line tool" as part of this change, since it's now user-facing for the first time).
  • internal/templating/model/presentation_model.go — the new ProjectType field.
  • internal/templating/post_generation_instructions.goBuildPostGenerationSummary's use of m.ProjectType in the SUMMARY headline.