Skip to content

fix: let returning builders see and edit their current-season milestones#341

Merged
tnrdd merged 2 commits into
mainfrom
fix/s3-builders-milestones-edit
Jul 3, 2026
Merged

fix: let returning builders see and edit their current-season milestones#341
tnrdd merged 2 commits into
mainfrom
fix/s3-builders-milestones-edit

Conversation

@tnrdd

@tnrdd tnrdd commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Problem

GoodBuilders S3 builders now in S4 reported the Milestones tab kept showing their old Season 3 milestones and that they could not edit.

  • The application-form save re-validated every milestone with an unconditional "description required" rule, while the round-49 textarea-to-milestone backfill left descriptions empty. Any full-form save failed on fields the builder never touched, and the milestones-tab PATCH (which allows empty descriptions when the element sets no minimum) disagreed with the PUT about the validity of the same stored data.
  • The milestones GET had no ordering, so the Season 3 section often rendered above Season 4.
  • The client tolerated blank deliverable rows that the server rejected, so a save could fail with no visible invalid field.

Fix

  • One shared schema (makeMilestoneDefinitionSchema via pickMilestoneDefinitionSchema) now defines milestone validity for the application PUT, the single-application PATCH, and the milestones-tab PATCH, so the write paths cannot drift.
  • Descriptions are mandatory, enforced as a ratchet: a description matching one still stored for that question is grandfathered (multiset matching, so adding, removing, or reordering milestones never re-triggers validation on untouched rows and net-new milestones cannot inherit an exemption); any edited or new description must be non-empty and honor the element's configured bounds. Both client forms mirror the rule against the server-stored baseline.
  • Milestones tab orders newest round first; GRADUATED applications stay visible and become writable only when the admin unlocks edits (previously they silently vanished from the tab).
  • Milestone deep links and card DOM ids now include the applicationId so multi-round projects cannot collide; pre-existing links resolve via a suffix-match fallback.
  • Blank deliverable rows are stripped at save time (matching the milestones-tab editor), and item validation errors regained the configured item label and 1-based row index.

Verification

  • Unit 313 passed, integration 141 passed, typecheck / lint / prettier clean.
  • Multi-agent adversarial review at high effort; all confirmed findings fixed (index-shift ratchet mismatch, GRADUATED write gate, draft-contaminated client baseline, duplicate DOM ids, error-message regression, unguarded stored-details parsing, exception path in the save handler).

Known edge left as is: stored legacy milestones containing blank item rows would fail an unchanged resubmit, but that is only reachable via raw API clients since both UIs filter blank rows before sending.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
platform Ready Ready Preview, Comment Jul 3, 2026 1:22pm

Request Review

@gaston-review gaston-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gaston Review

Verdict: Approved

Score: ████████░░ 8/10

Pull Request Summary

This PR makes two related changes: (1) it lets graduated builders see and edit their current-season milestones by widening the query filter from ACCEPTED-only to ACCEPTED | GRADUATED, gating writes on editsUnlocked, and (2) it introduces a "description ratchet" — unchanged milestone descriptions are grandfathered past validation rules, so legacy rows with empty or too-short descriptions don't block grantees from saving other edits. Deep links are also updated to include the applicationId for uniqueness across rounds, with a CSS selector fallback for backwards compatibility.

Review Summary

🟡 Warning — Potential TypeError on corrupted dynamic application details
In the milestones PATCH handler (line 431–433), the dynamic branch accesses appDetailsParsed.round[milestoneType] without optional chaining. If a stored dynamic application has malformed/missing round data, this will throw an unhandled TypeError and return a 500 instead of a graceful error. The legacy branch on the next line properly uses ?..

Solid, well-tested work. The ratchet pattern is non-trivial but the multiset approach handles edge cases (reordering, removal, net-new) cleanly, and the test suite covers them thoroughly. The one real concern is a potential null-dereference on corrupted dynamic app details in the milestones PATCH handler — low probability but easy to fix. Everything else is production-ready.

📌 4 inline comments
🟡 Warning: 1 · 🔵 Suggestion: 2 · ⚪ Nitpick: 1


🔍 Reviewed by Gaston

Comment thread src/app/api/flow-council/validation.ts
Comment thread src/app/api/flow-council/projects/[projectId]/milestones/route.ts
Comment thread src/app/api/flow-council/projects/[projectId]/milestones/route.ts
Comment thread src/app/projects/[id]/milestones/ProjectMilestonesTab.tsx

@gaston-review gaston-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gaston Review

Verdict: Approved

Score: ████████░░ 8/10

Pull Request Summary

This PR lets graduated builders view and edit their current-season milestones by widening query filters from ACCEPTED-only to ACCEPTED | GRADUATED, gating writes on editsUnlocked. It introduces a description ratchet — unchanged milestone descriptions are grandfathered past validation, so legacy rows with empty/short descriptions don't block saving other edits. Deep links now include applicationId for uniqueness across rounds, with a CSS selector fallback for old links. Blank deliverable rows are also stripped client-side before submission.

The prior review's one real concern — missing optional chaining on the dynamic branch in the milestones PATCH handler — has been fixed. The ratchet pattern is non-trivial but implemented consistently across both write paths (full-application PUT and milestones PATCH), with the multiset approach handling reordering, removal, and net-new milestones cleanly. Client-side validation mirrors the server logic. Twelve new test cases cover the ratchet edge cases thoroughly. Everything here is production-ready.


🔍 Reviewed by Gaston

@tnrdd tnrdd merged commit 845f353 into main Jul 3, 2026
4 checks passed
@tnrdd tnrdd deleted the fix/s3-builders-milestones-edit branch July 3, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant