Skip to content

Feature/dino UI restyle - #20

Open
tulas75 wants to merge 17 commits into
devfrom
feature/dino-ui-restyle
Open

Feature/dino UI restyle#20
tulas75 wants to merge 17 commits into
devfrom
feature/dino-ui-restyle

Conversation

@tulas75

@tulas75 tulas75 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

No description provided.

tulas75 and others added 15 commits August 10, 2026 12:54
Retire the top toolbar on wide viewports and give the sidebar three stacked
zones: brand and collapse toggle, grouped navigation (User / Administration),
and a utility footer with the sync control, the utility icons and the active
user card. Below 960px a slim bar (drawer toggle, logo, sync) replaces it and
the sidebar becomes an overlay drawer.

The toolbar previously carried twelve unrelated affordances while the sidebar
held only route links, and it spent ~65px of viewport on a bar with no page
context. The route links keep their existing config, permission gating and tour
anchors; most of the redesign is a re-layout rather than new behaviour.

Shell:
- Expose the layout as a `--dino-shell-*` custom property API, following the
  `--dino-survey-*` pattern already used by the public survey, and rewrite the
  pico deployment theme to override those tokens instead of Material internals.
- Drive all responsive rules off a single `dino-shell-compact` host class. The
  stylesheet used to declare 767/768/992px media queries alongside CDK
  breakpoint checks in the template, which disagreed between ~600 and 960px.
- Add a width-only `wide` breakpoint: CDK `Breakpoints.Web` requires 1280px in
  landscape, which would push an ordinary desktop window into the compact
  layout now that the sidebar is the only navigation.
- Size the sidebar and the page offset from the same host class rather than
  through `MatSidenavContainer.autosize`, which recomputed the content margin
  from a MutationObserver and read clientWidth mid-transition, leaving the
  content overlapped until an unrelated DOM change forced a re-measure.
- Add the group headings, the avatar initials, the active role label, the
  visible app version and the time elapsed since the last replication.

Page header:
- Rewrite `dino-breadcrumbs` in place as an overline plus title, so every call
  site picks it up unchanged, and add an `actions` slot for page level actions.
  Top level pages fall back to their section group, shared by the new
  `ShellContextService`.

Shared pieces:
- Extract the light/dark control into `@dino/material/theme-switch`, replacing
  the slide toggle in the shell and the copy of it in the login page.
- Extract `LangService` so the shell language menu and `dino-lang-selector`
  share one persistence path.
- Record `DataService.lastSyncAt` on every completed replication cycle.
- Drop the nav item styles that `_sidenav-theme.scss` duplicated from
  `main-nav.scss`, and the unused `setShowNavLabels` input.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On compact viewports the slim top bar already carries the logo and the drawer
toggle, so the sidebar's own brand row repeated both a few pixels below, giving
the open drawer two logos and two menu buttons.

Render the brand row only in the wide layout. The nav list becomes the first
child of the sidebar's flex column, which already has its own padding, so no
stylesheet change is needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The nav element carries flex: 1 1 auto, so it is what holds the sync control,
the utility icons and the user card down at the bottom of the sidebar. It was
dropped from the DOM while the sections loaded, which let that whole block ride
up under the logo for the length of the first replication, then snap back down.

Move the *ngIf onto the list inside it so the flexible zone stays mounted and
only its contents wait on the data.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…is up

The initialization screen leaves nothing to navigate to, so an expanded sidebar
spends the first replication showing links that cannot be followed. Collapse it
to the rail for the duration and restore whatever state it had once the data
lands.

The state is captured rather than assumed, so a deployment that configures
initialExtendedSidenav: false stays collapsed afterwards, and toggling the
sidebar by hand during the sync drops the pending restore.

Only on wide viewports: the compact drawer is already closed during the sync,
and opening it unasked would cover the page the user was waiting for. The
subscription takes just the first true/false pair, so the routed pages that push
their own loading state into the same subject never fold the sidebar away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dark logo set kept the orange and blue accent arcs from the colour version,
and the sidebar never reached it anyway: the default darkLogoPath pointed at
dino-bar-logo-light.svg, so dark theme rendered the navy wordmark on a #1b1f24
sidebar.

Recolour the two accent arcs to white in the three dark variants, matching the
monochrome-on-dark lockups on page 4 of the brand guide, and point the sidebar
default at dino-bar-logo-dark.svg, which was already in the repo but unused. The
geometry is untouched, so a deployment overriding logoDark is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The header carried the stacked lockup, which spends its height on a wordmark too
small to read at the 32px the brand row allows. Switch it to the horizontal one
from page 1 of the brand guide, in the sidebar brand row and in the compact top
bar.

The collapsed rail is 68px wide, so the lockup would shrink to an illegible strip
there: add a 'logoMarkPath' input and drop to the mark on its own while the
sidebar is collapsed. It falls back to logoImagePath when not given, so a
deployment with a single logo behaves as before, and dinoapp resolves it from the
same customImagesConfig keys - substituting the DINO mark under a configured
third party logo would be putting our brand on their shell.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dino-login-light.svg was an Illustrator export that painted its six shapes
through five opacity masks, each one a 442x130 embedded PNG. The geometry scaled
cleanly but the masks did not: anywhere the logo is drawn smaller than its native
size the browser downsamples them, which is why the header logo looked soft at
32px and sharp when opened on its own.

Redraw it from page 1 of the brand guide as six flat paths, no masks and no
rasters, in a group scaled into the 442x130 box the file already declared. Drop
in identical: overlaying the two at 200px shows no offset, and the file goes from
8.6K to 2.6K. The other five logo assets were already mask-free.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Toggling the theme applied it and emitted the change, but never wrote it down.
On the next start the service restored dino_theme_default, whose isDarkTheme is
only refreshed when a theme preset is saved, so the toggle was undone by whatever
the last save - or the environment config - had frozen in.

Store the choice under its own dino_dark_mode key on every setDarkMode call, so
every entry point persists it: the sidebar switch, the User Area dialog and the
login page alike. On start it is read before the first write and applied after
the preset is loaded, so it outranks the preset's own stale flag while a preset
loaded by hand from the User Area still applies the dark mode it was saved with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Opening a report or the dashboard spun the colour mark whatever the theme,
because both components bound a single lightSpinnerPath - and its default was
spinnerdino.png, a colour raster with no dark counterpart. On the dark page
background the navy ring all but vanishes.

The shell had resolved this correctly for its own spinner and its logos, so the
same twenty lines existed in three copies and two of them were wrong. Collect
them in themed-images.ts: one place naming the light/dark pair behind each role
(header lockup, rail mark, loading spinner) and one helper turning a pair into
the path for the active theme. All three components now read from it, and the
report loaders get the vector mark the shell already used.

Deployment overrides keep their existing meaning: a configured light image with
no dark variant is used in both themes rather than being replaced by ours.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Area utente was a 95vw dialog holding six accordion panels. The accordions were
there only to keep five unrelated settings groups from overflowing a format meant
for a single decision, backup and restore had no room for its confirmation and
result states, and the area had no URL: it could not be linked, refreshed or
reached with Back.

It is now a routed page at /user-area/:tab inside the shell, with the panels as
tabs and API Keys and Credits merged into one AI tab. The sidebar robot goes to
/user-area/ai, the user menu item to the page. No component was added beyond the
thin dinoapp page wrapper every route already has; the layout follows the mockup
but the chrome is the app's own, dino-breadcrumbs and mat-tab-group over the
--dino-shell-* tokens, so the page re-skins with a deployment theme.

Tabs are built as data and the index is derived from the same array that renders
them, so a hidden tab cannot leave the group pointing at the wrong panel. A slug
naming a tab the user does not have reports itself back for the host to correct.
trackBy keeps the bodies alive across a rebuild, so switching tabs does not
discard what was typed in the others.

Behaviour changes worth naming:
- Theme colors no longer repaint the app as they are picked. They drive a preview
  and reach the app on Salva tema, which means a preset name is now required to
  apply a color, as the design specifies.
- The API key is read only: masked, with show and copy. It is provisioned at
  login, so an account without one gets a line saying so rather than a field it
  cannot fill.
- The dark mode toggle moves from the dialog header to the Theme tab. The
  collapsed rail offers no theme control, so dropping it outright would have
  taken one away.

Also fixed, all exposed by the conversion:
- The section highlight matched section.url as a substring of the whole URL, so
  /user-area/ai lit up the AI item. It now matches the first path segment.
- A failed restore left the spinner running forever; only the dialog closing had
  ever stopped it.
- Cancelling a restore left the file input holding the file, so picking the same
  file again fired no change event.
- The theme preset autocomplete destructured the control value as an array and
  filtered on its first character.
- changePassword never set processing, so the submit button stayed live during
  the request.

The shell no longer imports UserAreaModule, so the page and its color picker,
clipboard and autocomplete dependencies leave the eager bundle for a lazy 35 kB
chunk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The version line at the foot of the user menu is a plain div, not a
.mat-mdc-menu-item, so it inherited no menu foreground and fell back to
near black - unreadable on the dark menu panel, and dimmed further by an
opacity of 0.6.

It now takes the same colour token the menu items use, which the theme
redefines under .isDarkTheme, and dims by colour rather than by an
opacity that would eat the contrast a second time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three defects in the restyled theme and backup tabs:

The colour picker was unusable. The directive sat on the matPrefix
swatch, so ngx-color-picker inserted its popup inside the form field,
where the field clipped it down to a strip of gradient. The directive
goes back on the mat-form-field - where it worked before the restyle -
and each field gains a positioned wrapper, so the popup is placed
against the field it belongs to instead of against the scrolling tab
body. That tab body no longer clips either: Material scrolls it, which
would cut off a popup hanging below the last field, and nothing here
needs its own scrollport because the shell scrolls the page.

The readonly hex took Material's dimmed readonly grey, which is not a
colour to read a hex in. It now takes the field's own foreground in the
mono face the rest of the page uses.

Restore rendered as bare text: mat-stroked-button was written on a span,
and the directive only matches button and a. It is a real button now,
opening the file input it sits beside.

Also hoists the three validation labels out of the template. The
formatter wraps a long interpolation, and a wrap inside a string literal
puts a newline into the message itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The routed user area landed with its strings in English and Italian
only, so the other dictionaries fell through to the English source text.

Audits the 46 keys the page uses against all eight dictionaries and adds
the 182 that were missing: 27 each to esp, fra and prt, 26 to ar and
ukr, 41 to uga and 4 to eng and ita. uga is an English placeholder
dictionary - every entry maps to its English string - so its additions
follow that convention.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The panel was a bare list of menu items: one line each, truncated to a
single row and readable only by hovering for the tooltip, with no sense
of when anything arrived and a mini-fab per row for the ones that led
somewhere.

It gets the structure the design asks for: a header carrying the unread
count and the mark-all action, a scrolling list under sticky day
headings, and a footer link. A row is now a dot, an icon, the text -
wrapped, not truncated - the age, and an arrow only when it goes
somewhere.

Two things are derived rather than stored. Notifications are grouped by
calendar day into Today, Yesterday and Earlier, headings following the
data so a day with nothing in it produces none. Within a day, a run of
repeats of the same text collapses into one row with a count, so a job
that notifies on every retry costs one row instead of ten; marking that
row read walks the whole run. Both live in notification-groups.ts as
pure functions, with the instant they measure against injected so they
can be asserted.

The rows are plain buttons rather than mat-menu-items: marking one read
is not a choice that ends the panel, so it must not close it. The two
entries that do leave - a row with a url, and the footer - close it
through the trigger themselves.

The panel is drawn in the CDK overlay, outside dino-main-nav, so the
--dino-shell-* tokens do not reach it. It carries its own neutrals on
the same scale, with the dark theme redefining only the values.

Also fixes mark-all-as-read, which patched every notification in the
collection without checking whether the user was already a reader, so
each click appended their id to `readers` a second time.

The mockup's sender name, initials avatar and title/body split are not
in this commit: the Notification model carries none of them, and the row
shows the notification's own type icon in their place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The restyled panel names its day groups, its ages and its unread count,
none of which existed as keys. Adds the nine new ones to all seven
translated dictionaries, plus the two the panel already used that uga
was missing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants