Observed while implementing #10952 (extending zeroFallback to every summary section). Filed unassigned — not routed, not graded. Neither of these is in #10952's scope, and #10952 deliberately did not touch either: triage there ruled on the four summary sections (Data/UI/Logic/Security), and both rows below sit outside that array.
Measured
packages/cli/src/utils/format.ts → printMetadataStats, at #10952's head.
1. Runtime: drops its whole row at zero, exactly like the sections used to
if(stats.plugins>0||stats.devPlugins>0){// …console.log(` ${chalk.bold('Runtime:')}${chalk.dim(parts.join(', '))}`);}A stack with no plugins and no devPlugins prints no Runtime: line at all. Confirmed against the real CLI (bin/run-dev.js validate, NO_COLOR=1) on a stack declaring nothing — the summary after #10952's fix is:
Data: 0 Objects
UI: 0 Apps
Logic: 0 Flows
Security: 0 Positions 0 Permissions
…and no Runtime: row. This is the same "reads as never asked, not as zero" shape #10504 and #10952 removed from the sections.
Why this was NOT folded into #10952. It is not mechanically determined by that card's evidence. Runtime: is not a member of the sections array — it has no items, it renders a different fragment style (2 plugins, 1 devPlugins, comma-joined and fully dim, versus the sections' count-then-item-name fragments joined by two spaces, as in Data: 1 Objects 2 Fields), and unlike an empty Security:, a stack with zero plugins is an entirely ordinary steady state rather than an unfinished one. Whether Runtime: 0 plugins is informative or is just a row that is always there saying nothing is a fresh product-judgment call, and inventing an answer inside #10952 would have been exactly the unilateral widening #10504's brief fenced off.
2. stats.translations is counted and then never printed
MetadataStats declares translations: number (format.ts:350) and collectMetadataStats populates it (format.ts:389, count(config.translations)), but printMetadataStats never reads it — grepping the function body for translations returns 0 hits. So every os validate / os info / os compile run does the work of counting a stack's translations and then discards the number.
This one is not a zero-drop at all: the count is invisible at every value, not just 0. A stack with 40 translation bundles reports them nowhere in the summary.
Open question for triage
Both come down to one question the #10952 ruling did not reach: does "a summary section is never silently dropped" extend past the sections array — to the Runtime: row, and to a counted metric that has no row at all? Plausible answers include giving translations a home in the existing UI: or a new i18n: fragment, dropping the unused field from MetadataStats under implementation-first, or leaving Runtime: conditional on purpose.
Suggested landing
packages/cli/src/utils/format.ts — same function, same file surface as #10952, once triage decides. If translations turns out to have no intended reader, the implementation-first answer is to delete the field rather than find it a row.
Context, not a blocker: #10504 (the original UI: 0 Apps ruling) and #10952 (its generalisation to all four sections).
Observed while implementing #10952 (extending
zeroFallbackto every summary section). Filed unassigned — not routed, not graded. Neither of these is in #10952's scope, and #10952 deliberately did not touch either: triage there ruled on the four summary sections (Data/UI/Logic/Security), and both rows below sit outside that array.Measured
packages/cli/src/utils/format.ts→printMetadataStats, at #10952's head.1.
Runtime:drops its whole row at zero, exactly like the sections used toA stack with no plugins and no devPlugins prints no
Runtime:line at all. Confirmed against the real CLI (bin/run-dev.js validate,NO_COLOR=1) on a stack declaring nothing — the summary after #10952's fix is:…and no
Runtime:row. This is the same "reads as never asked, not as zero" shape #10504 and #10952 removed from the sections.Why this was NOT folded into #10952. It is not mechanically determined by that card's evidence.
Runtime:is not a member of thesectionsarray — it has noitems, it renders a different fragment style (2 plugins, 1 devPlugins, comma-joined and fully dim, versus the sections' count-then-item-name fragments joined by two spaces, as inData: 1 Objects 2 Fields), and unlike an emptySecurity:, a stack with zero plugins is an entirely ordinary steady state rather than an unfinished one. WhetherRuntime: 0 pluginsis informative or is just a row that is always there saying nothing is a fresh product-judgment call, and inventing an answer inside #10952 would have been exactly the unilateral widening #10504's brief fenced off.2.
stats.translationsis counted and then never printedMetadataStatsdeclarestranslations: number(format.ts:350) andcollectMetadataStatspopulates it (format.ts:389,count(config.translations)), butprintMetadataStatsnever reads it — grepping the function body fortranslationsreturns 0 hits. So everyos validate/os info/os compilerun does the work of counting a stack's translations and then discards the number.This one is not a zero-drop at all: the count is invisible at every value, not just
0. A stack with 40 translation bundles reports them nowhere in the summary.Open question for triage
Both come down to one question the #10952 ruling did not reach: does "a summary section is never silently dropped" extend past the
sectionsarray — to theRuntime:row, and to a counted metric that has no row at all? Plausible answers include givingtranslationsa home in the existingUI:or a newi18n:fragment, dropping the unused field fromMetadataStatsunder implementation-first, or leavingRuntime:conditional on purpose.Suggested landing
packages/cli/src/utils/format.ts— same function, same file surface as #10952, once triage decides. Iftranslationsturns out to have no intended reader, the implementation-first answer is to delete the field rather than find it a row.Context, not a blocker: #10504 (the original
UI: 0 Appsruling) and #10952 (its generalisation to all four sections).