feat(frontend): work through the dashboard design audit - #13
Merged
Conversation
Four places reported failure by not reporting it, and two of them lost the message entirely. The console rendered an empty white rectangle whenever the log stream did not come up. The only signal was a toast, which faded after a few seconds and left no trace, so the panel was indistinguishable from a server that simply had nothing to say. It now carries its own state: connecting, connected but quiet, or disconnected with the reason and a Reconnect button. The command input is disabled while the stream is down instead of silently swallowing what gets typed into it. The three toasts are gone, because the information belongs where the reader is already looking. Toasts appeared in the top right corner, which is where the header keeps the account name and the logout button. While one was up, logging out was not clickable. They come up bottom right now, decided once in helpers/toast.js rather than per call site. Four files reached for `createToast` directly and picked a corner each; one login attempt could put its success message in one corner and its failure in another. The sign-in button was filled light grey on white, the same treatment this interface gives a disabled control, and the fields carried no label beyond a placeholder that disappears as soon as anything is typed. It is a primary button now, in the same blue the token page already uses, with real labels and the failure shown inside the dialog rather than behind it. Upload sat inside the file list as a rounded row the same size and shape as a directory entry, told apart from real content by the colour of a dot. It is a labelled drop area below the listing now, so an action no longer looks like content. Two defects surfaced while reading that code and are fixed here: - Nothing ever closed the log stream. `close()` on unmount was commented out, so every visit to the tab left another EventSource running against the server. Verified: one connection per mount, none left behind after leaving. - Every `.catch` that formatted an error read `error.response.status` and `error.response.data.message` directly. That works for a server that answered and throws for one that did not, and the throw happened inside the catch, taking the handler with it. A refused connection or an unreachable host, the most common failure of all, produced no message at all. There is one `errorMessage` helper now, lifted out of the tokens store where a correct version was already hiding, and the five call sites use it. Verified against the previous state view by view: overview and tokens come out pixel identical, settings differs only in the two bands where the toast moved from one corner to the other, and the dashboard only in its live values. Console is the intended redesign. Also removes five stray semicolons that eslint flagged in the console store, which the rewrite touched anyway. Errors on the branch drop from seven to two; both survivors sit in files this change does not open. Signed-off-by: TheMeinerLP <github@themeinerlp.dev>
The avatar, the description and the organisation link sat in the page header, so they were drawn above the console, above the settings form and above every other view an operator actually works in. That is 356px of chrome before the first line of content, on a window that is often 900px tall. They describe the repository, so they belong to the page that presents the repository. The overview tab renders them now and the header keeps the title, the account controls and the navigation. Console, settings and the dashboard each get 188px back. Two more places where the same choice was made twice: - The snippet card carried its own tab implementation, a row above 640px and a dropdown below it, while the main navigation scrolls sideways at that width. One choice, made two ways, depending on how wide the window happened to be. It is one scrolling row now, which also drops the dropdown's own bug: its open flag came back from localStorage as the string "false", which is true, so it never reopened after a reload. - The theme control cycles through three modes and announced the third one with a bare capital A between two icon buttons, where it read as a typography setting. It is a half-filled circle now, the usual glyph for "follow the system", and the button finally carries a label saying which mode it is in and what pressing it will do. With the dropdown gone, nothing uses the near-black ramp Windi called `dark` any more, so that block leaves src/style.css. The console divider, its last other user, takes gray-800 instead. Signed-off-by: TheMeinerLP <github@themeinerlp.dev>
TheMeinerLP
force-pushed
the
feat/frontend-navigation-chrome
branch
from
August 8, 2026 21:02
5cd9895 to
f9f3bdd
Compare
The backend groups resolved requests by statistics.resolvedRequestsInterval, which defaults to MONTHLY, and AllResolvedResponse carries no hint of which interval produced it. The chart subtitle asserted a daily granularity it could not know, and on a default instance it named the wrong one. Signed-off-by: TheMeinerLP <github@themeinerlp.dev>
The log panel was 36rem tall on every screen. That overflows a laptop and leaves a tall monitor half empty. It measures where it actually starts instead of subtracting a constant for the chrome above it. A constant would have to encode the height of the header, the tab bar and the filter row, and it would already be wrong: the header lost 188px one commit ago. A floor keeps a phone, where that chrome is most of the screen, from ending up with a two line console. The panel resolves to 606px at 1440x900, 406px at 1440x700 and 534px at 390x844, measured in a browser rather than computed. The level filter was six native checkboxes spread across a full row, each showing a count. It is a row of toggle chips now, which wrap instead of overflowing and are buttons rather than labels, so they are keyboard reachable and carry aria-pressed. State never rests on colour: a chip that is on has a solid border, a filled marker and a heavier label, and one that is off has a dashed border, a hollow marker and muted text. Warn and Error tint their own label while they are on, alongside those signals and never instead of them. Signed-off-by: TheMeinerLP <github@themeinerlp.dev>
The settings view restyled the JSONForms renderers of @dzikoysk/vue-vanilla by overriding their selectors from the outside, which means reaching into markup this repository does not write. The library exposes a styles object for exactly this, so the class names now come from there instead. configuration.js builds the object with mergeStyles on top of the defaults, and SettingsView provides it under the injection key the renderers read. Three renderers of our own take their class names from it as well, rather than hardcoding them. What stays in CSS cannot be expressed through the object: the split by input type shares one style key, `label` matches an element, the wrapper descendants include a bare paragraph and a `:read-only` state, `container` in a class attribute would additionally match the project's own rule of that name and add a page gutter, and the vue3-tabs markup passes through no key at all. Each of those rules now carries a comment saying why it is still there.
The token view carried a stylesheet of 53 @apply rules, every selector of which targeted markup the view renders itself. Each concept behind those rules is now a component of its own: a list row, a pill, a segmented control, a form field, an icon button, and the three inline editors. The utilities live in one place per concept rather than being described twice, once in the template and once in CSS. Name the row actions. Edit, regenerate and revoke were three grey icons of the same size with no accessible name between them. Each now carries a label naming the token it acts on, and the destructive one is outlined so it is told apart before any colour arrives on hover. Say what the expiry column means: "never expires" rather than an infinity glyph, "5 days left" rather than "5d left". Drop spark() and requests(), which invented a usage figure for a sparkline that was commented out. Ingot measures no per-token usage, and the unused function was the one eslint error in this directory. Indented form rows no longer inherit text-indent, which pushed the value of every input 24px away from the caret. Rows keep it, for the reason the comment above it gives.
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.
Description
Targets
maindirectly. Six commits, each self-contained, working through a design audit of the dashboard.What is in it
fix: tell the user what is wrong instead of showing nothing. The console rendered an empty white rectangle whenever the log stream failed, announced only by a toast that faded. It carries its own state now, with a reason and a Reconnect button. Toasts moved out of the top right corner, where they covered the logout button. The sign-in button was filled the same grey this interface gives a disabled control. Upload sat inside the file list looking like a directory row.Two defects found while reading that code: nothing ever closed the log stream, so every visit to the Console tab left another
EventSourcerunning; and every.catchthat formatted an error readerror.response.statusdirectly, which throws for a server that never answered, taking the handler with it, so the most common failure produced no message at all.feat: give the working views their screen back. The avatar, description and organisation link sat in the page header, above the console, the settings form and everything else. 356px of chrome before the first line of content. They describe the repository, so the overview tab renders them now: Console 1123px to 935px, Dashboard 1404px to 1216px, Settings 3056px to 2868px. The snippet card's own second tab implementation is gone, as is the bare capitalAthe theme control used to announce its third mode with.fix: stop claiming the request chart is grouped by day. The backend groups resolved requests bystatistics.resolvedRequestsInterval, which defaults to MONTHLY, andAllResolvedResponsecarries no hint of which interval produced it. The subtitle asserted a granularity it could not know, and named the wrong one on a default instance. My own error, from #10.feat: size the console to the viewport and compact its filters. The panel was 36rem tall on every screen. It measures where it actually starts rather than subtracting a constant for the chrome above it, which would already be wrong after the header change above. Measured in a browser: 606px at 1440x900, 406px at 1440x700, 534px at 390x844. The six level checkboxes are toggle chips that wrap instead of overflowing, are buttons rather than labels, and signal their state through border style, marker fill and label weight, never colour alone.refactor: style the settings form through the renderer API. The settings view restyled the JSONForms renderers of@dzikoysk/vue-vanillaby overriding their selectors from outside, reaching into markup this repository does not write. The library exposes a styles object for exactly this. The global block that reached into the library went from 24@applyrules to 13, and each survivor carries a comment saying why it cannot move.refactor: rebuild the token list out of components.TokensView.vuecarried 53@applyrules in a scoped stylesheet, every one of them targeting markup it renders itself. Pasting those declarations into class attributes would have been a step backwards, so the repeating pieces became components instead, the patternStatTile.vueandChartCard.vuealready set. The stylesheet is gone, the file went from 313 to 246 lines, the three row actions carry accessible names and the destructive one is distinguishable at rest,3y old · ∞reads3 years old · never expires, and a fake usage metric for a sparkline Ingot never measured is deleted.Type of change
fix- bug fixfeat- new featurerefactor- internal change without behaviour changeHow was this tested?
Pixel comparison, view by view, against the state before each change, five views times two themes times two viewports:
Where a view did change, the difference was located band by band rather than accepted: when the toasts moved,
settingsdiffered in exactly two horizontal bands, y 12-75 and y 824-887, and nowhere else.The console connection lifecycle was measured, not inferred: one request to
/api/console/logon first mount, still one after leaving the tab, two after returning, three after pressing Reconnect. No stream left behind.The token flows were driven end to end in a browser against the stub, because a refactor of that size cannot be reviewed by reading: search filtering, generating a token and seeing its secret once, editing a token and saving, adding a route, revoking with the confirmation cancelled, then revoking for real. All seven steps pass, no page errors.
Worth recording: the first three runs of that script reported failures which were all faults of the test, not the code. The route form has always submitted with
Addrather thanSave; the secret banner keeps the token name on screen, so asserting the row was gone matched the banner; and atext=/releases/...selector is read as a regular expression, whose leading slash makes invalid flags. The refactor had preserved every one of those behaviours exactly.npm run eslint: 7 errors before this branch, 1 after. Five were stray semicolons in the console store, one a dead variable in the token view../gradlew buildpasses.Checklist
CLAUDE.md./gradlew buildpasses locallyThe tests box stays unticked. The frontend has no test infrastructure and this branch does not add one. The verification above is reproducible but not committed, and the console connection lifecycle in particular deserves a real test.
Found along the way, not fixed here
StorageProvider.usage()returns-1on both the filesystem and the S3 backend, becausegetFileSizereturns-1for a directory and no recursive walk exists.FixedQuota.canHoldandPercentageQuota.canHoldare its only consumers, so they computemaxSize - (-1): storage quota enforcement is effectively a no-op. That is a backend defect with nothing to do with this pull request and should get its own issue.Still open from the audit
The Overview list itself. It comes from
GET /api/maven/details/, which returns{ "type": "DIRECTORY", "name": "releases" }per repository and nothing else. Of the four things worth showing there, visibility is already in memory on everyRepositoryand would cost one field on that response with no leak risk, since private and hidden repositories are filtered out before the caller sees them; a per-repository request count is free for managers today from the payload the dashboard already fetches; size and last deployment are both new backend work, the first needing a cached value rather than a directory walk per page load.