Repository navigation
feat(deployments): filter and page the list, show staged releases and how each node installed - #1874
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for staged deployments (Harper 5.3.0+) and peer install comparison fingerprints (Harper 5.3.1+). It updates the deployments list view with pagination, filtering by component and status, and enhances the deployment detail view to show installation sources, activation references, and detailed peer replication results. Feedback is provided on a minor improvement in the project input synchronization logic to ensure leading and trailing spaces are correctly normalized upon form submission.
| const projectInput = useRef<HTMLInputElement>(null); | ||
| useEffect(() => { | ||
| // Follows a filter set from outside the field, such as the editor's link, without remounting it. | ||
| if (projectInput.current && projectInput.current.value.trim() !== (project ?? '')) { |
There was a problem hiding this comment.
Using .trim() in the comparison projectInput.current.value.trim() !== (project ?? '') prevents the input field from being normalized/trimmed when the form is submitted. Since project is already trimmed when applied, comparing the raw value directly with project allows the input field to be automatically cleaned of leading/trailing spaces upon submission.
| if (projectInput.current && projectInput.current.value.trim() !== (project ?? '')) { | |
| if (projectInput.current && projectInput.current.value !== (project ?? '')) { |
There was a problem hiding this comment.
Declining. The field keeping what was typed isn't a defect: the filter itself is trimmed before it is applied (index.tsx:79). The suggested comparison would also normalize only when the filter changes. Resubmitting web while web is applied is a no-op at :80, so project doesn't change, the [project] effect (:98-103) doesn't run, and the padding stays. Normalizing every time would mean rewriting the field in onSubmit, which is a separate UX call for the reviewer.
🤖 Generated by Claude Code (Anthropic Claude Opus 5.5); posted via @dawsontoth.
… how each node installed The list filters by component and status, pages by the server's total, and shows each record's id, source (package, payload hash, or the release an activation made live) and user. A staged record (Harper 5.3+) is terminal and reads as waiting to be activated, so the list no longer polls it every 2s. The detail view shows the origin's install source, the activation's source release, and each peer's install comparison (5.3.1+), and drops the Restorable and Rollback-of fields no Harper version sets. The editor's Application menu links to the component's deployments. Refs #1786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ilter where staging is absent The list no longer keeps the previous page's rows on screen, clickable, while the next page loads. A saved ?status=staged link opened against an instance older than 5.3 no longer asks for a status it cannot have. The component filter applies on Enter only, so leaving the field to click Next does not reset the page under the click, and it follows an outside change without remounting the field. Refs #1786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the URL A saved ?status=staged link opened against Harper older than 5.3 left the URL claiming a filter while the list showed every status. Once the version is known, the page replaces the URL without it. Refs #1786 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Redeploy and Import offer Stage only where the instance reads 5.3.0 or later: it sends activate: false and no restart, and reports the staged deployment id without a health check. A staged record on the Deployments page has an Activate action, which sends deploy_component with its deployment_id and a rolling restart through the same mutation, so it is followed like any other rolling deploy; the progress panel ends a stage at Staged. All three request shapes come from one deployRequestBody, and the editor's Application menu links to a component's staged releases. Refs #1787 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sent twice Activate awaits the mutation instead of passing it callbacks, which React Query drops once the detail unmounts, so the report and the refresh still happen for an activation someone closed the dialog on. The mutation is keyed by entity and deployment, so a detail reopened mid-rollout shows it pending and offers no second activation. A stream that ends early says the outcome is unknown instead of reporting a failure. Refs #1787 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The activation reports every outcome itself, so it opts out of the global mutation toast, which would have called an early-ended stream a failure alongside the local 'may not be activated'. It logs the error with console.error as that handler did, so Datadog still sees it. Refs #1787 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…after it may have started When the SSE deploy got no event stream, onDeployComponentSubmit fell back to the buffered deploy_component on any SSEUnsupportedError: a gateway 502/503/504, a 2xx that was not a stream, a dropped connection, or the 120s idle watchdog firing before headers. Harper starts an SSE operation as soon as it accepts it and answers with a 200 stream, so each of those can follow a deploy that is already running, and the fallback deployed the same package a second time (a second install and rolling restart, or two deploys of one project racing). SSEUnsupportedError now carries the response status. The deploy falls back only on a 4xx, which Harper answers for a request it refused before running it (authentication, permissions, validation). Anything else becomes SSEInconclusiveError, which the import, redeploy and activate callers already answer by checking instead of resending. Closes #1889 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deployComponentStream's contract still told every caller to retry on SSEUnsupportedError, which is the resend this branch removes. Also trims two comments that restated the design note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
627b7d3 to
23beb6e
Compare
⊙ Problem
Stacked on #1868 (#1785), which stacks on #1824 → #1821 → #1817 → #1812. This diff is only the one commit above #1868. The #1787 PR stacks on this one.
Config → Deployments was built for 5.1's records. Since then a deploy can be staged and activated later (5.3.0), each node's install is compared with the origin's (5.3.1), and a restarting deploy is certified (5.4). Studio's types had none of it and
stagedwas not a status it knew, so a staged record read as still in flight: the list polled every 2s for as long as the page was open, and the detail view tried to tail it. The list fetched the newest 100 records with no filter or paging (Harper sets no default limit: harpercomponents/deploymentOperations.ts:90, so a component with a long history was simply cut off), and the detail view still showedRestorableandRollback of, which no Harper version sets:rollback_ofis alwaysnull(components/deploymentRecorder.ts:152, every tag since 5.1.0), andrestorablewas never on the row.💡 Solution
stagedis a status and is terminal, as Harper's own set has it (deploymentOperations.ts:19), which stops the polling. The record gainsactivated_fromandinstall_fingerprint, and each peerinstall,install_matchesandinstall_differs(fingerprint shape, harpercomponents/installFingerprint.ts:24).total. Columns are the id, component, status, source, user and start time. The source is the package reference, a payload's hash, or for an activation the release it made live, and a staged record reads as waiting to be activated.stagedis only offered as a filter on 5.3.0 and later.RestorableandRollback ofare gone.The status badge knows
staged. A row click keeps the filters when it opens the detail, and an empty page says the filters matched nothing rather than that there are no deployments.⚖️ Alternatives
successper component anddeployment.stagingRetention.maxCount. Kept releases share that count with staged builds, a release made live before 5.3.0 or from afile:directory is not kept, and a node prunes on its own schedule, so the inference would name ids that answer 404. Report which deployment is live on each node, and which releases each node keeps harper#3059 is the real fix.Not done (needs Harper)
successbefore a rolling job reaches the other nodes. The deploy flow follows that job since feat(deploy): follow a rolling deploy's restart job, and show canary certification #1868, but this page reads the record: A rolling deploy's record sayssuccesseven when another node rejects the release harper#3060.Refs #1786
✅ Verification
deploymentPolling.test.tsandtypes.test.tspinstagedas terminal for both polls;deploymentsSearch.test.tsthe URL validation and the 5.3 status gate;deploymentDescriptions.test.tsthe source and install-comparison text, including a pre-5.3.1 record that says nothing;index.test.tsxthat the URL's filters and page reachlist_deployments, that paging follows the server's total, that the component filter resets the page, and that staged and activation rows read as such.npx vitest run(exit 0; the pre-commit hook ran the full suite),npx tsc -b(exit 0),npx oxlint --format stylish .(exit 0),npx dprint check(exit 0).rolled_backout of the status filter on purpose: no Harper version writes it (finish('rolled_back')is never called). Gemini's round-2 blocker, thatuseDeploymentsAvailabledoes not exist, was wrong: it is exported fromuseSupportsDeploymentSSE.tsandtsc -bpasses. The Cursor legs could not run (the 1Password SSH agent refused theirgit fetch), and the Harper domain leg failed authentication.gemini-code-assist feedback. Declined: dropping
.trim()from the component field's sync effect. The applied filter is already trimmed, and the change would normalize the field only when the filter changes. Resubmittingwebwhilewebis applied changes nothing, so the effect never runs. The new head is only a merge of the updated #1868; this PR's own diff is unchanged.🤖 Generated with Claude Code
Related PRs: #1868 overlaps (base of this stack), #1824 overlaps (base of #1868; same deployments page and tests)
Complexity: medium
Review-Coverage: authored=claude; ran=gemini,codex; blocked=cursor-composer(no-receipt),domain(auth); declined=cursor-grok,cursor-kimi,cursor-muse; rounds=3; full=1 @ 627b7d3
Review-Attention: read ~8m (raised: degraded review) @ 627b7d3