Summary
cadenceDefaults is a static constant (DEFAULT_CADENCE), but it is only emitted on the success path. analyzeFromContent() returns early when the tracker has no rows:
// followup-cadence.mjs:797
export function analyzeFromContent(trackerContent, followupsContent = '') {
const apps = parseTrackerContent(trackerContent);
if (apps.length === 0) {
return { error: 'No applications found in tracker.' };
}
That payload carries no cadenceDefaults, so a user with an empty tracker gets no cadence defaults even though the values do not depend on tracker data at all.
Reproduction
On main at 8a20e49, with a tracker created exactly as the onboarding Step 4 in AGENTS.md creates it (header row, no entries):
$ CAREER_OPS_ROOT=/tmp/new node followup-cadence.mjs --json
{
"error": "No applications found in tracker."
}
$ echo $?
1
Positive control, same checkout, same command, one tracker row added:
$ CAREER_OPS_ROOT=/tmp/one-row node followup-cadence.mjs --json | grep -o cadenceDefaults
cadenceDefaults
Impact
readCoreDefaults() in web/src/app/api/followups/cadence/route.ts requires cadenceDefaults and returns null without it, so GET /api/followups/cadence responds with defaults: {} and effective: {} for a user who also has no followup_cadence: block in config/profile.yml.
cadence-settings.tsx maps effective onto its inputs and renders "" for any key the core did not supply, so all six fields on the Config page come up empty. Save is then rejected on the first empty field ("must be a whole number >= 0"), and nothing on the page states what the defaults are, so there is no value to type back in. The affected cohort is a fresh install: no applications tracked yet, no cadence overrides written yet.
The feature itself still behaves correctly, since resolveCadenceConfig() merges DEFAULT_CADENCE server-side. Only the settings form is affected.
Relationship to #3862
#3862 fixed the other half of this path by adding --json to KNOWN_FLAGS, and tests/web-core-argv-contract.test.mjs now pins the argv. That test covers which flags the core accepts, not what the payload contains, so this case is still open: the invocation now succeeds and the response is still missing the key the route needs.
Suggested fix
Include the defaults in the early return, since they are a constant and are already correct at that point:
return { error: 'No applications found in tracker.', cadenceDefaults: DEFAULT_CADENCE };
Additive, and consumers that branch on result.error are unaffected. Exit 1 should stay as it is: the analysis genuinely produced no entries, and the web route reads stdout rather than the exit code.
Adjacent observation
defaultsAvailable is emitted by the route but read by nothing in web/src. Its comment says it "tells the form to render its placeholders as unknown", and cadence-settings.tsx has no placeholder attribute on those inputs. Not part of this bug, but it means the intended honest-gap signal is not currently reaching the UI. Happy to split that out if it is worth tracking.
Environment
main at 8a20e49, package version 1.32.0.
Summary
cadenceDefaultsis a static constant (DEFAULT_CADENCE), but it is only emitted on the success path.analyzeFromContent()returns early when the tracker has no rows:That payload carries no
cadenceDefaults, so a user with an empty tracker gets no cadence defaults even though the values do not depend on tracker data at all.Reproduction
On
mainat 8a20e49, with a tracker created exactly as the onboarding Step 4 in AGENTS.md creates it (header row, no entries):Positive control, same checkout, same command, one tracker row added:
Impact
readCoreDefaults()inweb/src/app/api/followups/cadence/route.tsrequirescadenceDefaultsand returns null without it, soGET /api/followups/cadenceresponds withdefaults: {}andeffective: {}for a user who also has nofollowup_cadence:block inconfig/profile.yml.cadence-settings.tsxmapseffectiveonto its inputs and renders""for any key the core did not supply, so all six fields on the Config page come up empty. Save is then rejected on the first empty field ("must be a whole number >= 0"), and nothing on the page states what the defaults are, so there is no value to type back in. The affected cohort is a fresh install: no applications tracked yet, no cadence overrides written yet.The feature itself still behaves correctly, since
resolveCadenceConfig()mergesDEFAULT_CADENCEserver-side. Only the settings form is affected.Relationship to #3862
#3862 fixed the other half of this path by adding
--jsontoKNOWN_FLAGS, andtests/web-core-argv-contract.test.mjsnow pins the argv. That test covers which flags the core accepts, not what the payload contains, so this case is still open: the invocation now succeeds and the response is still missing the key the route needs.Suggested fix
Include the defaults in the early return, since they are a constant and are already correct at that point:
Additive, and consumers that branch on
result.errorare unaffected. Exit 1 should stay as it is: the analysis genuinely produced no entries, and the web route reads stdout rather than the exit code.Adjacent observation
defaultsAvailableis emitted by the route but read by nothing inweb/src. Its comment says it "tells the form to render its placeholders as unknown", andcadence-settings.tsxhas noplaceholderattribute on those inputs. Not part of this bug, but it means the intended honest-gap signal is not currently reaching the UI. Happy to split that out if it is worth tracking.Environment
mainat 8a20e49, package version 1.32.0.