Repository navigation
feat(webui): make the analytics dashboard the default landing page - #1044
Merged
Merged
Conversation
The Web UI now lands on the Dashboard's Usage (analytics) panel instead of the hub overview. `/usage` and `/overview` are added as deep-linkable routes rendering the same Dashboard component, so each panel survives a reload or a shared link and the tab switcher rewrites the URL (replace) without remounting the view or piling up history entries. First run (zero upstream servers) would show an empty chart grid, so the usage panel renders an "add your first server" CTA instead — no conditional routing, the landing page is the same for everyone. Roadmap: analytics-default-landing -> in_review (ROADMAP.md regenerated).
Deploying mcpproxy-docs with
|
| Latest commit: |
c6b6481
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://be1bddce.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://feat-analytics-default-landi.mcpproxy-docs.pages.dev |
…ing defects Cross-model review of the default-landing change surfaced three genuine defects in the promoted code path; all three are covered by regression tests that fail without the fix. - Dashboard tab switching lost the newest click. `selectView` compared the requested panel against `route`, which only updates once a navigation is confirmed, so a second click landing before `router.replace` resolved saw the pre-navigation route, issued no navigation of its own, and was then overwritten when the first navigation completed — leaving the panel the user clicked away from. Track the in-flight target so the newest click always navigates and the router cancels the superseded one. - The first-run CTA fired on a failed server fetch. `fetchServers` swallows transport errors (recording them on `loading.error` and leaving `servers` untouched), making a failure indistinguishable from "zero servers configured" — a user with a full server list was told they had none. Gate the CTA on the absence of a fetch error as well. - The usage panel could repaint with stale data. `reload()` has no request sequencing, yet the 30s auto-refresh, the window selector and the filter selects can all overlap; a slower earlier response landing last replaced the aggregate for the window the user had already moved to, and could also clear a spinner the newer request still owned. Sequence the requests so only the newest one writes state. Also hold the chart bundle back until the server list has arrived. Mounting UsageView first fired a usage aggregate request and flashed the "no usage data yet" card on a fresh install, a moment before the CTA replaced it. Both requests are issued together in onMounted, so this costs no extra round-trip.
…etch Second cross-model review round on the hardening pass; both findings verified against the code before fixing. - The tab-click marker could be cleared by the wrong navigation. It was owned by panel value, so two navigations to the same panel (Overview → Usage → Overview) shared an identity and the older one's settlement released a marker the newer one still owned — after which the next click compared against the stale current route and was swallowed. Reachable because the app's router guard is async (it dynamic-imports the auth store and may await checkAuth), so an aborted navigation settles only when its guard resumes. Own the marker by a monotonic token instead, mirroring the reloadSeq guard. - The first-run gate depended on which of two responses landed last. onMounted issued fetchServers() twice, unsequenced, and the gate read the store's shared loading.error live. Drop the duplicate request, and snapshot the outcome of the Dashboard's own fetch: loading.error is also written by silent background refreshes and is never cleared on success, so a live read let one transient failure suppress the CTA for the rest of the session. The same-panel marker case is fixed by construction rather than by test — vue-router's abort ordering could not be staged faithfully from the component harness, and a test encoding the wrong model would be worse than none. The burst test asserts the end state only; the other two findings have regression tests that fail without their fix.
…oaded Third cross-model review round; both findings verified before fixing, and they share one root cause: the Dashboard was inferring "did the server list load" from `loading.error`, a field it does not own. - An unrelated failure suppressed the first-run CTA. App.vue also calls fetchServers() on mount, concurrently with the Dashboard's own call, and silent background refreshes write the same shared field. If that other request failed first, the Dashboard snapshotted a failure that had nothing to do with its own successful fetch and hid the CTA. Removing the Dashboard's duplicate request last round narrowed the window but could not close it — the racing caller is in another component. - A successful refresh could never restore the CTA. The snapshot was taken once from the initial request, so an initial failure followed by a background refresh that succeeded and confirmed zero servers left the CTA gone for the rest of the session. Add a `loaded` flag to the servers store, set only when a list actually arrives successfully, and gate the CTA on that. It is monotonic and owned by the store, so a concurrent or later failure elsewhere cannot clear it and a later success does set it. The chart mount keeps a separate local flag for "our initial fetch has settled, successfully or not", so a failed fetch still falls through to the usage panel instead of spinning forever.
Contributor
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 32808978101 --repo smart-mcp-proxy/mcpproxy-go
|
Fourth cross-model review round. Both findings verified, and they converge on one root cause: `servers` has three independent writers that never cancel each other — a fetch from any component (App.vue and Dashboard.vue each issue one on mount), a silent background refresh, and the Spec 047 SSE full-list payload. - A stale response could resurrect an old list. Every successful fetch applied its payload unconditionally, so if the user added a server and the resulting refresh returned before an older in-flight request, that older response overwrote the list — dropping the new server and bringing the first-run CTA back. The same race lets a fetch clobber a list just delivered by SSE. - An SSE-delivered list did not count as loaded. The Spec 047 fast path applies an authoritative full list but left `loaded` false, so the usage panel kept spinning while a list was already in hand, and an empty SSE list could never raise the first-run CTA. Route all three writers through one applyServerList() that takes a ticket in issue order, drops anything older than the last applied list, and marks the store loaded. The Dashboard's chart gate now also clears on `loaded`, so a list arriving by SSE before the HTTP fetch settles ends the spinner immediately.
Fifth cross-model review round, one finding, verified before fixing. `deleteServer` removed the server from local state directly without advancing the list ticket introduced last round. A GET issued before the delete landed therefore still satisfied the sequencing check and re-applied the pre-deletion list, resurrecting the server the user had just deleted — and, on the last server, keeping the first-run CTA away until another authoritative list arrived. Route the removal through applyServerList() with a fresh ticket: the post-delete list is authoritative as of that moment, so anything already in flight is now correctly treated as stale. `addServer` needed no change — it awaits fetchServers(), which takes its own newer ticket.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
github-actions Bot
pushed a commit
that referenced
this pull request
Aug 25, 2026
…up, and "what needs me" on the landing page (#1049) * fix(webui): give first run a way forward (F3, F19) and keep "what needs me" on the landing page (F4) Web UI UX audit (docs/qa/ux-audit-webui-2026-08.md), first-run and onboarding. F3 — Servers page, zero servers. The page a new user lands on after closing the wizard said "No servers found / No servers available" and offered nothing to click, above four zero-valued stat tiles, four zero-count filter pills and a search box with nothing to search. It now renders the AgentTokens-style empty state: a headline, one line of why upstream servers matter, Add Server (primary), Browse Registry, and a quiet "Import from your AI client configs" link into the wizard's Servers step. The stat/filter/search chrome is hidden until a server exists, and "you have nothing yet" is now a separate state from "nothing matched your filter" — the two need different answers. F4 residual (after #1044) — #1044 routed /usage, made the analytics panel the default landing page and demoted the hub diagram, which is most of the fix. What it did not carry across is the other half the finding asked for: the landing page still answered "what happened" and not "what needs me". The servers-needing-attention and tools-pending-approval banners were inside the Overview panel, so a fresh landing could render charts while a server was down and an unreviewed tool was waiting, and say neither. They move above the panel switcher: visible on whichever panel is active, silent when there is nothing to act on. F19 — onboarding wizard, Servers step. It was entirely "pick which servers from your existing AI clients to import", so a user with no prior MCP config hit a dead end. Adds a nothing-to-import branch (browse the registry / add a server manually). Import & quarantine becomes the single primary and the unreviewed path becomes a link that says what it skips, "Import without review" — previously two similar primaries with the safer one rendered weaker. The Docker-isolation/quarantine panel is expanded by default and moved out of the sticky footer into the step body, where it can be open without squeezing the list; the import list is capped and scrolls in place instead of clipping its last row; and every step gets a Back. * fix(webui): initialise the wizard when it is mounted already-open, and keep every step exitable Cross-model review (opencode/gpt-sol) on the first-run changes. P1 — the Servers page's import link sets the store flag and then routes to the Dashboard, which is the component that mounts the wizard. The wizard therefore came into existence with `show` already true and never saw a transition to watch: it rendered, but the open sequence never ran. The onMounted fallback that existed for this case kicked off the fetches but never chose a tab and never consumed the request — so the wizard opened on Clients regardless of what the opener asked for, and left the request behind to hijack the next plain open. One `immediate` watcher now owns opening; the fallback is gone. The requested tab is read before the first await, so it cannot outlive its own open or be consumed twice, and it applies immediately so the wizard never paints the wrong step while the fetches are in flight. P2 — `goToRegistry` did not await `dismiss()`. `dismiss()` awaits its mark-skipped calls before emitting `close`, so routing first tore down the Dashboard that owns the open flag and the emit could land with nothing left to clear it, springing the wizard back open on return. P2 — the Servers page fetches on neither mount nor route: App.vue and the Dashboard do. That leaves a window where `loading` is false and the list is still unknown, in which the filter/search empty state claimed "no servers match your search criteria" about a list nobody had fetched. Adds an explicit pending state, gated so servers already in hand are never held behind it. Also restores Close to the import action footer, which the review asked about and the live run then proved: a step whose only exits are "import" strands the user, and the committed e2e sweep dismisses an auto-opened wizard through exactly that control — on a fresh instance the wizard can land on this step. The security disclosure gains `group` so its arrow reflects the now-open state. * fix(webui): abandon superseded wizard opens, and stop a failed refresh from hiding the server list Round 2 of the cross-model review (opencode/gpt-sol). P1 — nothing cancels the wizard's open sequence. A close, or a close followed by a reopen, while its five fetches were still in flight let the abandoned run finish and stamp its tab over the newer state — and call startPolling() on a wizard that was no longer open, leaving a 5s poll running until unmount. Each run now takes a generation token and checks it still owns the wizard before it writes; closing and unmounting both retire whatever is in flight. P2 — `loading.error` was write-only. Silent background refreshes set it and no success ever cleared it, so a single transient blip replaced the Servers page with a full-page "Failed to load servers" for the rest of the session, hiding a list we were still holding. A list that arrives now clears the error, and the blocking error state is reserved for the case with no fallback: when servers are already in hand, a failed refresh degrades to a non-blocking notice above the grid that says the data may be stale and offers Retry. * fix(webui): sequence the wizard's config read and the server-list failure path Round 3 of the cross-model review (opencode/gpt-sol). The open sequence's config read is the one fetch whose result the user can also edit: it drives the quarantine and Docker-isolation checkboxes, and this branch expands that panel by default, so toggling one the moment the step appears is now the expected path rather than a corner. A read left over from a superseded open landing afterwards would silently flip the checkbox back to the old server value. It now carries the open's generation token and does not commit if it has been superseded. The other fetches in the sequence write read-only display state that either run populates identically, so they are left alone. Server-list failures now take the same ticket as successes. Only the list itself was sequenced, so an older request failing after a newer one had already delivered a list would raise a stale error over fresh data — the mirror image of the stale-list problem the sequencing exists to prevent. * fix(webui): let a failed server fetch advance the sequencing mark Round 4 of the cross-model review (opencode/gpt-sol). Only a delivered list advanced the mark, so a newer request that failed left it behind — and an older list arriving afterwards still passed the check, applying stale servers over the newer outcome and clearing its error on the way. Both outcomes are news about the same list, so both now claim the ticket.
Dumbris
added a commit
that referenced
this pull request
Aug 26, 2026
The document read as an open P0-P3 backlog, but seven PRs merged on 2026-08-25 (#1044, #1048, #1049, #1051, #1052, #1053, #1054) closed it. Anyone opening this file against current main was being handed 36 stale defects. Added a status banner and a Resolution section that separates three things honestly: - Re-checked live on v0.61.0 against a running isolated core with real /mcp traffic: F1, F4, F5, F7, F8, F12, F13, F20, F24, F26, F33 confirmed fixed, each with the observed evidence rather than a PR reference. - Still open: F6 is half fixed. Focus enters the Add Server dialog and submit is in a sticky footer, but Escape still does not close it - AddServerModal.vue has no key handler and JsonViewer.vue is the only component under components/ that handles Escape at all. F28's "key called optional" half is fixed; its three-surfaces state is not. - Not re-checked: named explicitly rather than implied, so the next reader knows which closures rest on a PR body and which on an observation. Five defects found on main while re-checking are recorded separately, since they are not regressions of anything in the audit. The first is security-relevant: quarantining a server through the config file leaves its tools in the search index, so retrieve_tools still hands the agent their names and descriptions - which is exactly what quarantine exists to prevent - before the call is refused. Runtime.QuarantineServer purges the index; the reload path detects the same transition (quarantined_changed: true) and does not. Also corrects the record on #1053: its body names F1-F16, which is the tray audit's numbering mis-pasted. #1055 is the tray PR; #1053 touches no Swift file.
Dumbris
added a commit
that referenced
this pull request
Aug 26, 2026
The document read as an open P0-P3 backlog, but eight PRs merged on 2026-08-25 (#1044, #1048, #1049, #1050, #1051, #1052, #1053, #1054) closed it. Anyone opening this file against current main was being handed 36 stale defects. Added a status banner and a Resolution section that separates four things: - Re-checked live on v0.61.0 against a running isolated core with real /mcp traffic: F1, F4, F5, F6, F7, F8, F12, F13, F20, F24, F26, F33 confirmed fixed, each with the observed evidence rather than a PR reference. - Not re-checked: named explicitly rather than implied, so the next reader knows which closures rest on a PR body and which on an observation. F28 is called out as partial - its "key called optional" half is fixed, its three-surfaces-at-once state is not. - Corrections to the audit itself: F23 mis-diagnosed a per-request structural estimate as a stale aggregation, and F31's "Manual badge has no tooltip" half was wrong even at audit time (spec-088 trust-mode badge, already titled). - Five defects found on main while re-checking, recorded separately since they are not regressions of anything in the audit. The first is security-relevant: quarantining a server through the config file leaves its tools in the search index, so retrieve_tools still hands the agent their names and descriptions - which is exactly what quarantine exists to prevent - before the call is refused. Runtime.QuarantineServer purges the index; the reload path detects the same transition (quarantined_changed: true) and does not. F6 carries a methodology note. Re-checking it produced a false negative: Escape driven through the browser-automation layer never reaches the page's document keydown listener, so the dialog looked stuck open. Dispatched in-page it closes at once, and #1050's useModalA11y is wired into all five <dialog :open> modals. The sweep assertion this document proposes must dispatch in-page, or it tests the automation layer instead of the app. Also corrects the record on #1053: its body names F1-F16, which is the tray audit's numbering mis-pasted. #1055 is the tray PR; #1053 touches no Swift file.
Dumbris
added a commit
that referenced
this pull request
Aug 26, 2026
Verified against v0.60.0 rather than taken on trust: - The tray Settings tab is named "Raw", not "Raw JSON" (native/macos/MCPProxy/MCPProxy/Views/SettingsView.swift:38 at v0.60.0). The §1.2 inventory, the §2 parity matrix and F6's body all said "Raw JSON". - The Web UI had no /usage route at v0.60.0 - the router's path list runs /, /servers, /repositories, /search, /settings, /secrets, /sessions, /tools, /activity, /security, /tokens with no usage or overview entry. Usage was a Dashboard tab; /usage became a route later, in #1044. The parity matrix's "Usage report | ⚠ savings % on Dashboard | ✅ /usage | inconsistent" overstated the Web UI side of that comparison. Also noted in §6 that three §1.1 menu labels have changed since the walk as part of the F9 fix: Open MCPProxy Window, Open Web UI in Browser, Launch at Login.
Dumbris
added a commit
that referenced
this pull request
Aug 26, 2026
* docs(qa): Web UI UX audit — ranked findings (ux-audit epic) Heuristic + live-instance audit of every routed Web UI view in light/dark, first-run and populated states, at 1440/820/390px. 36 findings ranked P0-P3 with view, evidence, heuristic violated, suggested fix and effort. Audited against standard heuristics plus the house activity-UI rules (grouping, failure-only status, intent reason, histogram/log parity). * docs(qa): record what the Web UI audit's backlog actually resolved to The document read as an open P0-P3 backlog, but eight PRs merged on 2026-08-25 (#1044, #1048, #1049, #1050, #1051, #1052, #1053, #1054) closed it. Anyone opening this file against current main was being handed 36 stale defects. Added a status banner and a Resolution section that separates four things: - Re-checked live on v0.61.0 against a running isolated core with real /mcp traffic: F1, F4, F5, F6, F7, F8, F12, F13, F20, F24, F26, F33 confirmed fixed, each with the observed evidence rather than a PR reference. - Not re-checked: named explicitly rather than implied, so the next reader knows which closures rest on a PR body and which on an observation. F28 is called out as partial - its "key called optional" half is fixed, its three-surfaces-at-once state is not. - Corrections to the audit itself: F23 mis-diagnosed a per-request structural estimate as a stale aggregation, and F31's "Manual badge has no tooltip" half was wrong even at audit time (spec-088 trust-mode badge, already titled). - Five defects found on main while re-checking, recorded separately since they are not regressions of anything in the audit. The first is security-relevant: quarantining a server through the config file leaves its tools in the search index, so retrieve_tools still hands the agent their names and descriptions - which is exactly what quarantine exists to prevent - before the call is refused. Runtime.QuarantineServer purges the index; the reload path detects the same transition (quarantined_changed: true) and does not. F6 carries a methodology note. Re-checking it produced a false negative: Escape driven through the browser-automation layer never reaches the page's document keydown listener, so the dialog looked stuck open. Dispatched in-page it closes at once, and #1050's useModalA11y is wired into all five <dialog :open> modals. The sweep assertion this document proposes must dispatch in-page, or it tests the automation layer instead of the app. Also corrects the record on #1053: its body names F1-F16, which is the tray audit's numbering mis-pasted. #1055 is the tray PR; #1053 touches no Swift file.
Dumbris
added a commit
that referenced
this pull request
Aug 27, 2026
* docs(qa): macOS tray UX + settings-parity audit Read-only sweep of the running v0.60.0 tray (accessibility-tree menu walk) plus a static comparison of the tray's native surfaces and settings catalogue against the Web UI. Parity matrix (feature surfaces, settings fields, per-server actions) and 16 ranked findings: dead menu-bar health badge, unlabelled status item, silent action failures, unreachable Agent Tokens view, and three settings fields that drifted out of the tray catalogue. * docs(qa): mark the tray audit resolved, and say what was not re-verified The document read as an open backlog, but #1055 ("fix(tray): close the 16 findings of the macOS tray UX + settings-parity audit", merged 2026-08-25) fixed all 16, with #1056 as a catalogue follow-up. The header's "no version skew, so every finding below is reproducible from source" is no longer true against main. - Header scopes that claim to the time of the walk and carries a status banner. - New §6 Resolution records the two fixes whose obvious implementation is wrong: F1's severity dot is an attributed-title glyph, not a composite image (the status image stays isTemplate and a template image renders monochrome), and F6 added a parity gate rather than just the three missing keys. - Verified on main rather than taken on trust: Menu/TrayIcon.swift is deleted, TokensView is reachable from Views/MainWindow.swift, MCPProxyApp.swift sets isTemplate + accessibilityDescription + toolTip + attributedTitle glyph, and SettingsCatalog.swift carries deep_scan / instructions / code_execution_max_parallel. Note for anyone tracing this later: #1053's body claims to fix these 16 tray findings, but #1053 touches no Swift file — it is the Web UI cluster its own title names. #1055 is the tray PR. The caveat is stated rather than hidden: this pass could not re-walk the live tray, because macOS Accessibility was revoked on the reviewing machine (check_accessibility -> trusted:false), so the resolution is read from the #1055 diff, not from a second walk. * docs(qa): correct two §1 claims that were wrong at the time of the walk Verified against v0.60.0 rather than taken on trust: - The tray Settings tab is named "Raw", not "Raw JSON" (native/macos/MCPProxy/MCPProxy/Views/SettingsView.swift:38 at v0.60.0). The §1.2 inventory, the §2 parity matrix and F6's body all said "Raw JSON". - The Web UI had no /usage route at v0.60.0 - the router's path list runs /, /servers, /repositories, /search, /settings, /secrets, /sessions, /tools, /activity, /security, /tokens with no usage or overview entry. Usage was a Dashboard tab; /usage became a route later, in #1044. The parity matrix's "Usage report | ⚠ savings % on Dashboard | ✅ /usage | inconsistent" overstated the Web UI side of that comparison. Also noted in §6 that three §1.1 menu labels have changed since the walk as part of the F9 fix: Open MCPProxy Window, Open Web UI in Browser, Launch at Login. * docs(qa): re-walk the live tray and record that the findings still hold Replaces the "could not re-walk" caveat with the walk itself. macOS Accessibility was granted, so mcpproxy-ui-test works now; the walk reproduces this audit almost verbatim, because the installed tray is still v0.60.0 with "Update 0.61.0 — ready to restart?" sitting in the menu. Reproduced live: F2 (empty status-bar title, no description, no tooltip), F5 (no Agent Tokens entry), F7 (ElevenLabs "Stop" vs cloudflare-graphql "Disable"; and memory showing status Disabled above action Start), F8 (all three quarantined servers offer only Disable/Stop, Restart, View Logs), F11 (research and deploy still offered at 0 tools), F12 (streamable-http beside http and sse), F15 (29 flat entries), F16 (no tool search), and the pre-#1055 menu labels. One drift worth noting: the Clients block now reads "No clients in the last 24h" rather than listing an idle client - the empty state H5 exists to avoid. The consequence for the resolution section: #1055's fixes are real in the diff but remain unverified against a running tray. That needs the pending 0.61.0 restart or the dev bundle-swap, neither of which this pass did - restarting the tray kills the core it manages.
Merged
3 tasks done
Dumbris
added a commit
that referenced
this pull request
Aug 29, 2026
…inish the ux-audit sweep (#1085) * chore(roadmap): truth-sync ux-audit and upgrade-nudge 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. * feat(079): "N releases / M weeks behind" upgrade delta (FR-002) 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. * test(ux-audit): finish the sweep's regression net, and fix what it caught 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.
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.
Roadmap
analytics-dashboard/analytics-default-landing(the epic's only remaining task)./now opens the Dashboard on its Usage (analytics) panel; the tab order is Usage, Overview./usageand/overviewadded as deep-linkable routes rendering the sameDashboardcomponent (meta.dashboardViewpicks the panel). Tab clicksrouter.replaceto the matching path, so a panel survives a reload/shared link without remounting the view or piling up history entries./keeps itsdashboardroute name, so every existing link and guard redirect still works.in_review,ROADMAP.mdregenerated.Tests: new
frontend/tests/unit/dashboard-default-landing.spec.ts(route config, landing panel, deep links, URL rewrite on tab click, first-run CTA);dashboard-usage-switcher.spec.tsupdated for the flipped default. Full vitest suite 671 passed,vue-tsc --noEmitclean. Verified live against an isolated instance with a Playwright sweep (landing panel, CTA, deep links + reload, panel with a server configured, no page exceptions).