Skip to content

feat(frontend): work through the dashboard design audit - #13

Merged
TheMeinerLP merged 6 commits into
mainfrom
feat/frontend-navigation-chrome
Aug 9, 2026
Merged

feat(frontend): work through the dashboard design audit#13
TheMeinerLP merged 6 commits into
mainfrom
feat/frontend-navigation-chrome

Conversation

@TheMeinerLP

@TheMeinerLP TheMeinerLP commented Aug 8, 2026

Copy link
Copy Markdown

Description

Targets main directly. Six commits, each self-contained, working through a design audit of the dashboard.

This also re-lands #11. That pull request was merged into feat/tailwind-dashboard-ui, but by then #10 had already been squash-merged into main, so its target had become a dead end and its content never reached main. Checked rather than assumed: main contained neither Reconnect in ConsoleView.vue nor bottom-right in helpers/toast.js. The commit rides along here unchanged, so nothing is lost and #11 needs no further action.

Once this merges, feat/tailwind-dashboard-ui and fix/frontend-broken-states are both stale and can be deleted.

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 EventSource running; and every .catch that formatted an error read error.response.status directly, 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 capital A the theme control used to announce its third mode with.

fix: stop claiming the request chart is grouped by day. The backend groups resolved requests by statistics.resolvedRequestsInterval, which defaults to MONTHLY, and AllResolvedResponse carries 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-vanilla by 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 @apply rules to 13, and each survivor carries a comment saying why it cannot move.

refactor: rebuild the token list out of components. TokensView.vue carried 53 @apply rules 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 pattern StatTile.vue and ChartCard.vue already 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 · ∞ reads 3 years old · never expires, and a fake usage metric for a sparkline Ingot never measured is deleted.

Type of change

  • fix - bug fix
  • feat - new feature
  • refactor - internal change without behaviour change

How was this tested?

Pixel comparison, view by view, against the state before each change, five views times two themes times two viewports:

View Result
Overview 0, untouched
Settings 0 light, 7 pixels dark, antialiasing. The renderer-API refactor is invisible, which was its condition
Tokens changed as intended
Console changed as intended
Dashboard live values plus the subtitle correction

Where a view did change, the difference was located band by band rather than accepted: when the toasts moved, settings differed 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/log on 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 Add rather than Save; the secret banner keeps the token name on screen, so asserting the row was gone matched the banner; and a text=/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 build passes.

Checklist

  • Commit messages follow the Conventional Commits convention documented in CLAUDE.md
  • Tests cover the change, or the change is not testable and I explained why above
  • ./gradlew build passes locally
  • Documentation and configuration samples are updated if the change is user-facing
  • Existing copyright headers are preserved; new files carry the appropriate header
  • No credentials, tokens or personal data are included in the diff

The 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 -1 on both the filesystem and the S3 backend, because getFileSize returns -1 for a directory and no recursive walk exists. FixedQuota.canHold and PercentageQuota.canHold are its only consumers, so they compute maxSize - (-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 every Repository and 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.

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
TheMeinerLP force-pushed the feat/frontend-navigation-chrome branch from 5cd9895 to f9f3bdd Compare August 8, 2026 21:02
@TheMeinerLP
TheMeinerLP changed the base branch from fix/frontend-broken-states to main August 8, 2026 21:02
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.
@TheMeinerLP TheMeinerLP changed the title feat(frontend): give the working views their screen back feat(frontend): work through the dashboard design audit Aug 8, 2026
@TheMeinerLP
TheMeinerLP merged commit b14183d into main Aug 9, 2026
7 checks passed
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