Repository navigation
feat(079): "N releases / M weeks behind" upgrade delta (#1081), and finish the ux-audit sweep - #1085
Merged
Merged
Conversation
Two epics claimed a state the repo contradicts. upgrade-nudge was `done` with all four tasks done, but Spec 079 FR-002 — the human-readable "N releases / M weeks behind" delta required on status, doctor, the startup log, the Web UI and both trays — was never built. The four shipped tasks are US-slices and the FR belonged to none of them; spec 079 has only spec.md, so no checkbox surface could catch the gap. It survived only as TODO(spec-079/FR-002) comments in internal/updatecheck/checker.go and cmd/mcpproxy/status_cmd.go, which is how an outside contributor found it (#1081). Epic back to in_progress with an explicit upgrade-nudge-delta task. ux-audit was `todo` with both sweeps todo, but both shipped in August (#1046 Web UI, 36 findings; #1043 macOS tray, 16 findings) along with ten PRs closing every finding and the five defects the re-check pass raised (#1061/#1064/#1065, all closed). Recorded the sweeps and fix waves as done and added the two items that really are open: folding the audit's F6 modal Escape/focus and F1 cross-view total assertions into e2e/web-ui-sweep, and verifying the tray fixes on a running tray (the 2026-08-26 re-walk found the installed tray still on v0.60.0, so #1055 remains unobserved). Also cleared the two --check-github errors, neither introduced here: - tpa-db and mcp-2026-upgrade each linked a docs(specs) PR in `pr:`. That field is implementation evidence, so a spec merge read as "epic done". Both entries' own notes already said implementation had not started; moved the refs into the notes and left the statuses honest at todo. - analytics-default-landing (#1044) and schema-deferred (#1063, tasks.md 89/89) were genuinely finished and are now done.
Closes the last outstanding slice of Spec 079. FR-002 requires the current version, the latest version AND a human-readable delta on `mcpproxy status`, `doctor`, the startup log, the Web UI and both trays; what shipped was latest-version-only, and the deferral survived as TODO(spec-079/FR-002) comments that an outside contributor found (#1081). The blocker was data, not rendering: the checker fetched only /releases/latest, so it had the offered release's publish date but not the running one's, and no tag sequence to count. internal/updatecheck/delta.go computes the delta as a pure, network-free, clock-free function, so every spec rule is a table row. Fetching is a best-effort enrichment hung off the existing check — never a second check pipeline (FR-001): - one page of /releases (ETag-conditional, so a repeat costs no rate-limit budget), plus a by-tag lookup only when the running build predates that page and its date is therefore unknown; - both requests share one context deadline. A per-request timeout bounds each hop and nothing bounds the total, and CheckNow runs synchronously inside GET /api/v1/info?refresh=true, so an unbounded delta would stall a request a user is waiting on; - counting is by semver, never by list position: GitHub orders the list by created_at, and the live data has v0.53.0-rc.9 sitting ahead of the later-published rc.10. Three degradation rules matter more than the feature. A delta miss must not set CheckError (statusVersionSuffix returns "" whenever it is non-empty, so that would DELETE the nudge instead of enriching it), must not touch the FR-018 backoff counters, and must leave every surface rendering exactly what it rendered before. There is a regression test per rule. The clause is rendered once in Go (FormatBehindSummary) and printed verbatim by every surface, with the raw numbers alongside for machine consumers. That is deliberate: GuidanceLine is already hand-ported into UpdateBanner.vue with a "keep the two in sync" comment, and FR-002 asks for consistent framing across five surfaces. Fields are additive per FR-021 and had to be threaded through six hand-maintained mirrors — VersionInfo, the InfoResponseUpdate DTO and its copier, internal/contracts (→ make swagger), the contracts.ts generator, status_cmd's map decoding, and the Swift tray's Codable, which turned out never to have decoded the US2 fields either. Two adjacent bugs fixed because the delta sits on the same code: - GetLatestReleaseIncludingPrereleases returned releases[0] from a created_at-ordered list, so an RC user could be offered a release older than one already published. Now max-by-semver. - GetReleaseByTag interpolated an unvalidated tag into the request path and deferred validation to callers by comment only. The delta feeds it the ldflags-stamped version, so it now validates and escapes. Both trays render the delta in the update item's tooltip rather than its title: Spec 092 FR-010 fixes the title's exact shape, and a menu row reading "Update v0.62.0 — ready to restart? — 8 releases / ~14 weeks behind" stops being scannable. Known bound, accepted deliberately: the scan reads one 100-release page, so a build older than that window reports a lower bound ("59+ releases") and a semver-in-range release created long ago can be missed without setting that flag. Spec 079's own assumption (L159) allows an approximation where an exact count is unavailable, and the figure is motivational, not load-bearing. Verified: unit + integration tests against an httptest GitHub (delta, saturation, tag fallback, 304 reuse, budget, malformed tags), `go test -race` across internal and cmd, golangci-lint v2 clean, 962 frontend unit tests, and a live version-stamped instance confirming the degradation path renders the pre-delta output when the check fails.
…ught The August Web UI audit closes by asking for four assertions to be folded into the committed sweep. Three were already there (F9 contrast, F30 accessible names + aria-live, F14 responsive); this adds the two that were not, and then fixes the defects they immediately exposed on main. F6 — the Add Server modal takes focus, traps Tab, closes on Escape and restores focus to its trigger. These `<dialog :open>` modals are opened by the `open` attribute rather than showModal(), so none of that comes from the browser; it all comes from useModalA11y, whose own comment delegates the real-browser half of its coverage to "the Playwright sweep" — a promise the sweep did not keep. Escape is dispatched IN-PAGE, never via page.keyboard.press: re-checking F6 during the audit produced a false negative for exactly that reason, because a synthetic key never reaches the document listener under test. F1 — Activity and Usage must report the same 24h numbers as the API. The invariant is not naive equality: Usage folds `blocked` into its error tile while Activity splits them, and /activity/usage is served from a short read cache while /activity/summary counts live, so the assertion polls for convergence and encodes the real relationship. Running the completed sweep turned up three defects already on main, none of them regressions of the audit's 36: 1. The LANDING PAGE failed AA in both themes (3.09-4.23:1) and two of its controls had no accessible name. Usage.vue uses the raw `opacity-*` utility for muted text; #1054 fixed that failure mode for the text-base-content ramp and .stat-* but never covered raw opacity, and nothing caught it because the audit walked `/` before #1044 made Usage the landing page. Moved onto the AA-checked --tone-muted; both selects got an aria-label. 2. The Activity status pill clips at 390px for any status outside the known four. An unmapped status falls through to a ghost pill carrying the raw value, and `tool_auto_approved` measured 145px inside a 93px cell (the table is table-fixed below 640px). The pill now truncates against the cell and keeps the full value in its title, rather than the column being sized to today's longest label — which would hold only until the next status is added. The phone check that finally caught #2 had been passing vacuously: it measures rows.first(), and a `success` row — ~90% of traffic — renders an sr-only label of zero width. It only became a real assertion once the new F1 check generated an error row and put one first. That coupling is worth knowing about: the sweep shares one instance, so one check's traffic is the next check's data, and a newly-failing neighbour after adding a check is more likely a real find than a flake. Both were separated from the change under test by running the same sweep against clean HEAD. Sweep: 25 passed / 3 failed on clean HEAD, 30 passed / 0 failed now. Roadmap: ux-audit was still carrying both sweeps as `todo` although they and every finding shipped in August; recorded here along with what the completed net found.
Deploying mcpproxy-docs with
|
| Latest commit: |
4f6f4a1
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b10fb206.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://claude-issue-1081-roadmap-df.mcpproxy-docs.pages.dev |
…dmap-df1ca5 # Conflicts: # ROADMAP.md # roadmap.yaml
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Contributor
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 33253006954 --repo smart-mcp-proxy/mcpproxy-go
|
This was referenced Aug 29, 2026
Dumbris
added a commit
that referenced
this pull request
Aug 31, 2026
…ithub warnings (#1136) * chore(roadmap): truth-sync statuses against code, clear all --check-github warnings An evidence-based re-derivation of every non-done roadmap.yaml leaf. Each flip below was verified against the tree, git/gh state and the rendering sites, then independently re-checked by an adversarial pass; claims that did not survive that pass were dropped rather than applied. Statuses that were wrong in the "already shipped" direction: - upgrade-nudge-delta todo -> done (#1085, closes #1081). The note asserted TODO(spec-079/FR-002) markers exist and that the checker calls only /releases/latest; both are false. internal/updatecheck/delta.go now computes the delta and all six surfaces render it. - upgrade-nudge epic in_progress -> done (all five tasks now done). - analytics-dashboard in_progress -> done. Both children shipped; the default-landing half is live (router '/' -> Dashboard, guarded by dashboard-default-landing.spec.ts). Spec 069's one open task is a local Playwright sweep that leaves no artifact and can never tick from code. - discovery-eval-harness todo -> done. Both halves ship: cmd/scan-eval for security recall/FP, and the eval.yml retrieval-d1 gate plus bench/'s own RecallAtK/NDCGAtK + recall@5 parity gate for discovery quality. - hygiene-spec-gardener in_progress -> done (#824, #870, #999, #842). Its recorded blocker is cleared: check-spec-evidence.py is on main. Statuses that were wrong in the "claimed progress it does not have" direction: - windows-tray and windows-tray-window in_review -> todo. No Windows-tray PR is open or merged and native/windows/ is a README placeholder, so "in review" pointed at nothing. The section header is renamed to match. - planning-hygiene todo -> in_progress and action-log-transparency todo -> in_progress; both already have done children. Notes corrected where they would misdirect the next implementer: - token-bench-harness claimed it absorbs discovery-eval-harness by measuring retrieval recall. Recall is already measured, and bench/ already covers token counting, encoding arms (incl. direct_deferred), break-even and reporting. The note now records what is genuinely open: activity-log session replay, measured first-call success/retry rate, and tokens-per-successful-task. - token-bench-public: the MCP-specific third already ships via spec 083 (ToolRet, LiveMCPBench snapshot, lap-score); only agent-loop suites remain. specs/098-tools-preflight/tasks.md: tick T001, T024, T031, each confirmed against the tree (doc.go states both invariants T001 requires; toolslist_snapshot_test.go is labelled T024 and green; README carries all three FR-017 examples). T025/T027/T032/T033 are left unchecked — they are one-time process acts or did not survive verification. Deliberately NOT changed: no epics were added for shipped specs (084-100). roadmap.yaml is the working set by design and ROADMAP.md already renders a generated per-spec progress table, so absence of an epic is not drift. scripts/gen-roadmap.py --check-github: 3 warnings -> 0 errors, 0 warnings. * fix(roadmap): correct six claims flagged by cross-model review All six findings from the opencode gpt-5.6-sol review were independently re-verified against the tree before being applied; none were false positives. (The reviewer's initial claim that spec-gardener-auto-approve.yml is missing WAS a false positive from its glob tool and it self-corrected — the file exists at .github/workflows/spec-gardener-auto-approve.yml.) 1. Un-tick specs/098 T024. T024 asks for a golden snapshot test proving tools/list is BYTE-IDENTICAL across all three modes. Spec 099 (FR-014/015) later converted that test from a no-delta gate into an ENUMERATED-delta gate (toolsListAllowedDelta permits describe_tool and quarantine_security to move), so at HEAD the test no longer demonstrates the property T024 states. It may well have held on 098's own branch, but that is not checkable from the tree, and an unverifiable tick is exactly what this PR exists to remove. Badge 29/33 -> 28/33. 2. discovery-eval-harness note: drop the "SUPERSEDED — folded into token-bench-harness" framing. It contradicted itself once the epic was marked done — token-bench-harness is still unbuilt, so nothing could have been folded into it. Both halves shipped independently. 3. Same note: the eval.yml retrieval-d1 job is REPORT-ONLY on pull requests (continue-on-error, guarding against npx/uvx fetch flake) and blocks only on the nightly schedule. Promoting it to PR-blocking is still open (MCP-742). 4. Same note: the SC-003 recall@5 = 0.68 +/- 0.05 parity gate is asserted in bench/armindex_test.go, not armindex.go — the latter supplies the production index wiring, not the assertion. 5. token-bench-harness note: the literature-derived retry defaults live in bench/session.go (armRetryRates, research D8). bench/report.go only renders and labels them as estimates. The substantive point stands: retry rates are assumed, not observed. 6. token-bench-public note: "CI-wired" overstated the LiveMCPBench snapshot. bench.yml's regular job passes only -corpus-v2; the snapshot is runnable via -livemcptool but not run in CI, and its loader has no retrieval golden set, so it measures token/scale rather than recall. ToolRet does have its own bench.yml job. gen-roadmap.py --check and --check-github both still clean. * fix(roadmap): tighten two claims from cross-model review round 2 Both findings verified against the tree before applying. 1. The retrieval-D1 blocking scope was stated too narrowly. eval.yml sets continue-on-error only for github.event_name == 'pull_request', and the workflow triggers on pull_request, workflow_dispatch and schedule — so the job is report-only on PRs and BLOCKING on both nightly and manual dispatch runs, not "only on the nightly schedule". The note now says so, and records that the workflow's own inline comment carries the same omission. 2. discovery-eval-harness is now done but still declared depends_on: [token-bench-harness], which is unbuilt — a done node cannot depend on an open one, and the edge contradicted the corrected note saying both halves shipped independently. Edge removed; the id remains a valid depends_on target for anything that needs it. gen-roadmap.py --check and --check-github both clean. * fix(roadmap): scope four over-broad claims from cross-model review round 3 All four verified against GitHub and the tree before applying. 1. windows-tray: "no Windows-tray PR is open or merged" was false. #74 ("feature: windows tray support") merged 2025-10-23 and the cross-platform Go/systray build still ships on Windows. The claim now scopes to what the epic actually tracks — the native WebView2 replacement — for which no PR exists and native/windows/ is a README placeholder. 2. analytics-dashboard: "can never tick from code" conflated two things. A human can run spec 069's T023 Playwright sweep and tick the box; what can never exist is code evidence for it. Reworded so the distinction is explicit. 3. hygiene-spec-gardener: "three gardener runs ... auto-approved by #842" was wrong. Only #999 carries a github-actions APPROVED review. #824 has none and merged ~24 minutes BEFORE the auto-approve workflow landed; #870 records no approving review either. 4. token-bench-public: calling the MCP-specific third "integrated" overstated completion. ToolRet and LiveMCPTool are static retrieval/token corpora and lap-score is a linter — none is a public agent-loop task suite, which is what the task ultimately owes. Also noted for the record: the linux-arm64 Build Binaries failure on the previous commit is a flaky test, not a regression from this docs-only change. TestServiceStartScanDeepOnRunsSourceResolutionAndPass2 (internal/security/scanner) passes locally with -count=3, and two runs of this same branch four minutes apart — 33360429077 and 33360648947, both docs-only — disagreed on it. * fix(roadmap): ToolRet is not MCP-specific (cross-model review round 4) Verified: bench/corpusio/toolret.go documents ToolRet as a general tool-retrieval corpus whose documentation fields are "heterogeneous JSON/text, NOT MCP input schemas" (Schema stays nil). Grouping it under "the MCP-specific third" was wrong. The token-bench-public note now says public corpus/linter groundwork is integrated while NO public agent-loop task suite is, and names the ToolRet caveat explicitly so the next implementer does not re-inherit the error. Round 4 found no other issues; the round-3 windows-tray, analytics T023 and gardener corrections were all confirmed accurate against GitHub and the tree. * fix(roadmap): discovery-eval-harness is in_progress, not done (review round 5) The final review round caught an error in this PR's own flip, and it is the same failure mode the PR was written to fix: an epic marked done while a governing FR is unmet. Spec 065 FR-009 and SC-005 require CI to FAIL on a discovery regression beyond tolerance. The retrieval-D1 job sets continue-on-error for pull_request (D1 depends on npx/uvx fetches, a known flake source), so on the PR path CI does not fail — and eval.yml's own comment records the promotion to PR-blocking as still planned (MCP-742). The HARNESS shipped; the GATE did not. Reverted to in_progress and added the remaining work as a child task (discovery-eval-pr-blocking, MCP-742) so the epic's status has something in the DAG to point at. The reviewer also read CN-002 (frozen corpus, never live) as violated because D1 boots a live mcpproxy serving seven reference servers. Recorded as a weaker tension to adjudicate rather than a violation: #931 pinned all seven upstreams to freeze-era versions and the job gates on the exact corpus ID set, so the corpus is reproducible in practice. Whether that satisfies CN-002 or a committed snapshot is required is a judgement for the spec owner. gen-roadmap.py --check and --check-github both clean (0 errors, 0 warnings).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
Description
Closes the last outstanding slice of Spec 079, and finishes the
ux-auditepic's regression net.1. Spec 079 FR-002 — the "N releases / M weeks behind" delta (#1081)
FR-002 requires the current version, the latest version and a human-readable delta on
mcpproxy status,doctor, the startup log, the Web UI and both trays. Only latest-version-only ever shipped; the deferral survived asTODO(spec-079/FR-002)comments, which is how an outside contributor found it.The blocker was data, not rendering: the checker fetched only
/releases/latest, so it had the offered release's publish date but not the running one's, and no tag sequence to count.internal/updatecheck/delta.go— new, pure (no network, no clock, no locks), so every spec rule is a table row./releases, plus a by-tag lookup only when the running build predates that page.CheckNowruns synchronously insideGET /api/v1/info?refresh=true, so an unbounded delta would stall a request a user is waiting on./releasesbycreated_at, and the live data hasv0.53.0-rc.9sitting ahead of the later-publishedrc.10.Three degradation rules matter more than the feature, each with a regression test. A delta miss must not set
CheckError(statusVersionSuffixreturns""whenever it is non-empty, so that would delete the nudge instead of enriching it), must not touch the FR-018 backoff counters, and must leave every surface rendering exactly what it rendered before.The clause is rendered once in Go (
FormatBehindSummary) and printed verbatim everywhere, with raw numbers alongside for machine consumers.GuidanceLineis already hand-ported intoUpdateBanner.vuewith a "keep the two in sync" comment; FR-002 asks for consistent framing across five surfaces, so authoring it once is the cheap way to get that.Fields are additive per FR-021 and had to be threaded through six hand-maintained mirrors —
VersionInfo, theInfoResponseUpdateDTO and its field-by-field copier,internal/contracts(→make swagger), thecontracts.tsgenerator,status_cmd's map decoding, and the Swift tray'sCodable, which turned out never to have decoded the US2 fields either.Two adjacent bugs fixed because the delta sits on the same code:
GetLatestReleaseIncludingPrereleasesreturnedreleases[0]from acreated_at-ordered list, so an RC user could be offered a release older than the one they run. Now max-by-semver.GetReleaseByTaginterpolated an unvalidated tag into the request path, deferring validation to callers by comment only. The delta feeds it the ldflags-stamped version, so it now validates and escapes.Both trays put the delta in the update item's tooltip, not its title: Spec 092 FR-010 fixes the title's exact shape, and a menu row reading
Update v0.62.0 — ready to restart? — 8 releases / ~14 weeks behindstops being scannable.2. ux-audit — finish the sweep's regression net, and fix what it caught
The August Web UI audit closes by asking for four assertions in
e2e/web-ui-sweep. Three were already there (F9 contrast, F30 names/aria-live, F14 responsive); this adds the two that were not.page.keyboard.press: re-checking F6 during the audit produced a false negative for exactly that reason, because a synthetic key never reaches thedocumentlistener under test.blockedinto its error tile while Activity splits them, and/activity/usageis served from a short read cache, so the assertion polls for convergence.Running the completed net turned up three defects already on
main, none regressions of the audit's 36:Usage.vueuses rawopacity-*; fix(webui): AA contrast, system theme, mobile layout and a11y gaps #1054 fixed that failure mode for thetext-base-contentramp and.stat-*but never covered raw opacity, and nothing caught it because the audit walked/before feat(webui): make the analytics dashboard the default landing page #1044 made Usage the landing page.tool_auto_approvedmeasured 145px in a 93pxtable-fixedcell. The pill now truncates against the cell rather than the column being sized to today's longest label.The phone check that caught #2 had been passing vacuously: it measures
rows.first(), and asuccessrow (~90% of traffic) renders ansr-onlyzero-width label. It only became a real assertion once the new F1 check put an error row first.3. Roadmap truth-sync
upgrade-nudgewasdonewith FR-002 unbuilt;ux-auditwastodowith both sweeps and every finding actually shipped in August. Also cleared both--check-githuberrors:tpa-dbandmcp-2026-upgradeeach had adocs(specs)PR inpr:(implementation evidence), so a spec merge read as "epic done" — moved to the notes, statuses left honest attodo.Testing
go test -race, CI's own skip regex.github/.golangci.yml)HEAD: 25 pass / 3 fail)HEADmake swagger--checkclean,--check-github0 errors (was 2)Live end-to-end, real GitHub API,
v0.30.0-stamped core — all six surfaces agree:Numbers verified independently, not just for self-consistency: v0.30.0 (2026-05-12) → v0.62.0 (2026-08-28) is 108 days = 15 weeks, and GitHub serves exactly 47 stable releases in that range. Correctly not saturated, since page 1 reaches back to v0.26.2.
That check also justified a guard that looked like over-engineering: there are 48 stable-looking tags in the range but 47 releases. The extra one is
v0.36.0, which carries a cleanvX.Y.Ztag but is flaggedprerelease: trueon GitHub. Requiring both a falseprereleaseflag and an empty semver prerelease segment is what excluded it — trusting either signal alone would show a stable user the wrong number.Notes for the reviewer
59+ releases), and a semver-in-range release created long ago can be missed without setting that flag. Spec 079's own assumption (L159) permits an approximation where an exact count is unavailable, and the figure is motivational, not load-bearing.deltaScanPagesis a named constant if that ever needs raising.deltaFunchad a data race. All three fixed and re-reviewed as fixed; the fourth is the page-boundary bound above.ux-audit-tray-live-verifyroadmap task. The Go tray renders from the samebehind_summaryfield the CLI does.AppLifecycleTestsjournal failure is environmental and reproduces identically on cleanHEAD— I checked before attributing it.Closes #1081.