Dashboard layout: Apps card header (R-909), launcher header wraps on a phone (R-907), overflow counted after the page filter (R-906)

- Apps card: title row (logo top-aligned, name, address cut with an ellipsis and full in title) over a wrapping
  .stack-tags row; the tags no longer share a space-between row with the name.
- Phone (768px block): .page-header wraps; the share button drops to its own line, text whole.
- GetBannerAlerts(page, lang): the layout's filter before the cap; baseData and /monitoring use it.
- Tests: TestStackCardHeaderLayout, TestR907_PageHeaderWrapsOnAPhone, TestR906_* (all red against 05e12921).
- No Hungarian string changed. The three stacks parity fixtures are re-captured in the next commit
  (this commit alone fails TestI18nParity on them; both are pushed together).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
2026-10-08 14:32:06 +02:00
parent 05e129267a
commit d5f2e47ea1
8 changed files with 330 additions and 9 deletions
@@ -24,12 +24,12 @@
<div class="stack-title-row">
<img class="stack-logo-lg" src="{{logoURL .Meta.Slug}}" alt="" data-fallback="{{if $im}}/static/infra-logo.svg{{else}}/static/app-placeholder.svg{{end}}"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='{{logoPNGURL .Meta.Slug}}';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<div>
<div class="stack-title-text">
<h3>{{if $im}}{{$im.DisplayName}}{{else}}{{.Meta.DisplayName}}{{end}}</h3>
{{$subdomain := index $.Subdomains .Name}}
{{if and $subdomain (or .Deployed .Protected)}}
<a class="subdomain-link" href="https://{{$subdomain}}.{{$.Domain}}" target="_blank">
{{$subdomain}}.{{$.Domain}} ↗
<a class="subdomain-link" href="https://{{$subdomain}}.{{$.Domain}}" target="_blank" title="{{$subdomain}}.{{$.Domain}}">
<span class="subdomain-text">{{$subdomain}}.{{$.Domain}}</span> ↗
</a>
{{if and .Deployed (routeUnpublished .State)}}
<span class="route-unpublished" title="{{T "stacks.a_proxy_traefik_csak_egeszseges"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "stacks.url_nem_elerheto_utvonal_nincs"}}</span>
@@ -37,6 +37,7 @@
{{end}}
</div>
</div>
<div class="stack-tags">
<span class="tag tag-{{stateColor .State}}"><span class="dot"></span>{{stateLabel .State}}</span>
{{if .Orphaned}}<span class="tag tag-warn">{{T "common.elavult"}}</span>{{end}}
{{template "meta_badge" (lifecycleBadge .Meta)}}
@@ -44,6 +45,7 @@
{{$ms := index $.MissingStorage .Name}}{{if $ms}}<span class="tag tag-warn" title="{{T "stacks.az_alkalmazas_adattaroloja_nem_elerheto"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "stacks.hianyzo_tarhely"}}</span>{{end}}
{{$ns := index $.NetworkStubs .Name}}{{if $ns}}<span class="tag tag-warn" title="{{T "stacks.az_alkalmazas_kornyezeteben_a_halozati"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "stacks.halozati_tarhely_hibas_az_alkalmazas"}}</span>{{end}}
{{$nw := index $.NetworkWarnings .Name}}{{if $nw}}<span class="tag tag-warn" title="{{T "stacks.a_halozati_tarhely_nas_jelenleg"}}"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>{{T "stacks.halozati_tarhely_nem_elerheto"}}</span>{{end}}
</div>
</div>
{{if $im}}
+31 -3
View File
@@ -447,24 +447,48 @@ h3 {
.stack-detail-card.stack-state-run::before,
.stack-detail-card.stack-state-progress::before { background: var(--blue); }
.stack-detail-card.stack-state-warn::before { background: var(--warn); }
/* Card header, the same on every card (2026-10-08): row 1 = logo + name (+ the address under it), row 2 =
the tags, left-aligned and wrapping. The tags used to share one space-between row with the name and
covered it once an installed app had two of them; the logo was centred on the name block, so a card
with an address carried it lower. Pinned by TestStackCardHeaderLayout. */
.stack-detail-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-direction: column;
gap: .6rem;
margin-bottom: .75rem;
}
.stack-title-row {
display: flex;
align-items: center;
align-items: flex-start;
gap: .75rem;
min-width: 0;
}
.stack-title-text {
flex: 1;
min-width: 0;
}
.stack-title-text h3 { margin-bottom: .25rem; overflow-wrap: anywhere; }
.stack-title-text .route-unpublished { white-space: normal; }
.stack-tags {
display: flex;
flex-wrap: wrap;
gap: .4rem;
}
.subdomain-link {
display: flex;
align-items: baseline;
gap: .3rem;
max-width: 100%;
font-size: .8rem;
color: var(--blue-bright);
text-decoration: none;
transition: color 0.2s ease;
white-space: nowrap;
}
.subdomain-text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.subdomain-link:hover { text-decoration: underline; }
/* Tag — square 2px state chip with a 6px dot. Color only on deviation:
@@ -2675,6 +2699,10 @@ a.stat-card:hover {
.app-row { flex-wrap: wrap; }
.app-row-actions { width: 100%; justify-content: flex-end; }
.stack-grid { grid-template-columns: 1fr; }
/* R-907: a page header (title, domain chip, buttons) wraps instead of running off the screen; on the
launcher the share button drops to its own line. Pinned by TestR907_PageHeaderWrapsOnAPhone. */
.page-header { flex-wrap: wrap; row-gap: .6rem; }
.share-open-btn { margin-left: 0; white-space: nowrap; }
.stats-grid { grid-template-columns: repeat(3, 1fr); }
.deploy-info { flex-direction: column; }
.system-info-items { flex-direction: column; gap: 1rem; }