Otevrena stranka sekala prehravani videa
Pri otevrenem portalu zacalo vedle nej sekat prehravani videa, po zavreni bylo hned dobre. Neslo o smycku v kodu: zivy prenos udalosti je SSE, jedno spojeni na celou aplikaci, a setTimeout je v nem jen na odstup pri znovupripojeni. Byly to tri veci. Rozmazavani plne barvy. `.glass`, podklad skoro kazdeho panelu, mel backdrop-filter blur(14px). Jenze pod kartami je plna barva (bg-ink-950 na dashboardu), takze se rozmazavala jednolita plocha: zadny rozdil videt nebyl a kazda karta si za to drzela vlastni rozmazanou vrstvu ve skladaci. V kodu je glass na 58 mistech, na jedne obrazovce jich je snadno dvacet. Rozmazani zustava na peti mistech, kde pod nim neco opravdu projizdi: lepici hlavicky, podklad modalu, prekryv menu na mobilu. Nekonecne animace nad velkymi rozmazanymi plochami. Prihlaseni a verejny web mely zare o velikosti 26 az 34 rem s blur(120px), kterym se donekonecna animovala pruhlednost - to znamena prepocitavat obrovskou vrstvu sedesatkrat za sekundu, i kdyz se na strance nic nedeje. Zare zustaly, animace zmizela. Pribylo prefers-reduced-motion. Kazda udalost prekreslila cely dashboard. EventStreamProvider mel v tomtez kontextu stav spojeni, odber i seznam poslednich udalosti. Seznam se meni pri kazde udalosti, takze se menila cela hodnota kontextu a prekreslil se kazdy, kdo si jen zaridil odber - tedy vsechny dotazy s refetchOn na strance, i kdyz o ten typ udalosti nestaly. Pri behu automatizace se cely dashboard prekresloval nekolikrat za sekundu, a s nim dvacet rozmazanych vrstev. Kontext je proto rozdeleny: useEventStream da stav a odber, useEventLog seznam udalosti. Filtr sekci nefiltroval. Seznam ticketu sklada adresu dotazu v useMemo, ale v zavislostech chybely groupId, typeId, tag a stage - adresa se po prepnuti sekce neprepocitala a data se nenacetla znovu. Driv to nevadilo, byly to filtry nastavitelne jen odkazem, ktere se po nacteni nemenily. Se zalozkou "Vsechny tickety" a jejim vyberem sekce je to chyba, ktera je videt. Overeno: groupId=grp_servicedesk vrati jeden ticket, groupId=none zbyle dva. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9f4b8c0596
commit
0c405ea55a
@@ -144,3 +144,29 @@ zakaznika v ukazkovych datech a prejmenovat ho nema duvod.
|
||||
| Materialy mimo web | prezentace, obrazky pro socialni site |
|
||||
| Domena | `worknuke.cz` v `brand.ts`, ale nikde neni nasazena |
|
||||
| Prochazeni `accent-*` | 341 mist, ktera uz nemaji duvod pouzivat druhy klic |
|
||||
|
||||
## Rozmazani jen tam, kde neco prekryva
|
||||
|
||||
`.glass` je podklad skoro kazdeho panelu a **nema `backdrop-filter`**.
|
||||
|
||||
Puvodne mel `blur(14px)`, jenze pod kartami je plna barva (`bg-ink-950` na
|
||||
dashboardu): rozmazavala se jednolita plocha, zadny rozdil videt nebyl a kazda
|
||||
karta si za to drzela vlastni rozmazanou vrstvu ve skladaci. Pri dvaceti
|
||||
kartach na obrazovce to vytizilo GPU natolik, ze vedle portalu sekalo
|
||||
prehravani videa.
|
||||
|
||||
Rozmazani patri jen tam, kde pod nim neco opravdu projizdi:
|
||||
|
||||
| Kde | Trida |
|
||||
| -------------------------- | ----------------- |
|
||||
| lepici hlavicka dashboardu | `backdrop-blur-xl` |
|
||||
| lepici hlavicka webu | `backdrop-blur-xl` |
|
||||
| podklad modalu | `backdrop-blur-sm` |
|
||||
| prekryv menu na mobilu | `backdrop-blur-sm` |
|
||||
|
||||
**Zadna nekonecna animace nad velkou rozmazanou plochou.** Zare na prihlaseni
|
||||
a na webu jsou staticke: animovat pruhlednost objektu s `blur(120px)` znamena
|
||||
prepocitavat obrovskou vrstvu porad dokola, i kdyz se na strance nic nedeje.
|
||||
|
||||
Kdo ma v systemu vypnute animace (`prefers-reduced-motion`), nedostane ani
|
||||
ty zbyle.
|
||||
|
||||
@@ -2,6 +2,60 @@
|
||||
|
||||
Nejnovejsi nahore.
|
||||
|
||||
## 2026-09-08 - Otevrena stranka sekala prehravani videa
|
||||
|
||||
Pri otevrenem portalu zacalo vedle nej sekat prehravani videa, po zavreni
|
||||
stranky bylo hned dobre. Neslo o smycku v kodu: **zivy prenos udalosti je SSE,
|
||||
jedno spojeni na celou aplikaci**, a `setTimeout` je v nem jen na odstup pri
|
||||
znovupripojeni. Zdrzeni bylo jinde a byly to tri veci.
|
||||
|
||||
### Rozmazavani plne barvy
|
||||
|
||||
`.glass`, tedy podklad skoro kazdeho panelu, mel `backdrop-filter: blur(14px)`.
|
||||
Jenze **pod kartami je plna barva** (`bg-ink-950` na dashboardu), takze se
|
||||
rozmazavala jednolita plocha: zadny rozdil videt nebyl a kazda karta si za to
|
||||
drzela vlastni rozmazanou vrstvu ve skladaci. V kodu je `glass` na 58 mistech,
|
||||
na jedne obrazovce jich je snadno dvacet.
|
||||
|
||||
Rozmazani zustava tam, kde pod nim neco opravdu projizdi: lepici hlavicka
|
||||
dashboardu i webu, podklad modalu a prekryv menu na mobilu. To je pet mist,
|
||||
kazde se svym `backdrop-blur-*` primo u sebe.
|
||||
|
||||
### Nekonecne animace nad velkymi rozmazanymi plochami
|
||||
|
||||
Prihlaseni a verejny web meli zare o velikosti 26 az 34 rem s `blur(120px)`,
|
||||
kterym se **donekonecna animovala pruhlednost**. Animovat pruhlednost silne
|
||||
rozmazaneho objektu znamena prepocitavat obrovskou vrstvu sedesatkrat za
|
||||
sekundu, porad, i kdyz se na strance nic nedeje.
|
||||
|
||||
Zare zustaly, animace zmizela. Krome toho pribylo `prefers-reduced-motion`: kdo
|
||||
si v systemu animace vypnul, nema je dostat ani tady.
|
||||
|
||||
### Kazda udalost prekreslila cely dashboard
|
||||
|
||||
`EventStreamProvider` mel v tomtez kontextu **stav spojeni, odber i seznam
|
||||
poslednich udalosti**. Seznam se meni pri kazde udalosti, takze se menila cela
|
||||
hodnota kontextu - a prekreslil se kazdy, kdo si jen zaridil odber. To jsou
|
||||
vsechny dotazy s `refetchOn` na strance, i kdyz o ten typ udalosti nestaly.
|
||||
|
||||
Pri behu automatizace, ktera posila `automation.run` a `ticket.updated`, se tak
|
||||
cely dashboard prekresloval nekolikrat za sekundu - a s nim dvacet rozmazanych
|
||||
vrstev.
|
||||
|
||||
Kontext je proto rozdeleny: `useEventStream` da stav a odber, `useEventLog`
|
||||
seznam udalosti. Seznam si bere jen ten, kdo ho opravdu vypisuje, tedy toasty.
|
||||
|
||||
### Filtr sekci nefiltroval
|
||||
|
||||
Seznam ticketu sklada adresu dotazu v `useMemo`, ale v zavislostech chybely
|
||||
`groupId`, `typeId`, `tag` a `stage`. Adresa se tedy po prepnuti sekce
|
||||
neprepocitala a data se nenacetla znovu - filtr vypadal, ze nedela nic.
|
||||
|
||||
Driv to nevadilo: byly to filtry, ktere sly nastavit jen odkazem a po nacteni
|
||||
stranky se uz nemenily. Se zalozkou "Vsechny tickety" a jejim vyberem sekce se
|
||||
z toho stala chyba, ktera je videt.
|
||||
|
||||
Overeno: `groupId=grp_servicedesk` vrati jeden ticket, `groupId=none` zbyle dva.
|
||||
## 2026-09-07 - Podminka se muze ptat na vic veci naraz
|
||||
|
||||
Podminka byla **prave jedna otazka**. Slozitejsi vetveni se muselo skladat
|
||||
|
||||
@@ -3,10 +3,16 @@ import type { ReactNode } from 'react';
|
||||
import { connectEventStream, type StreamStatus } from '@/lib/eventStream';
|
||||
import type { DashboardEvent, DashboardEventType } from '@/types/events';
|
||||
|
||||
/**
|
||||
* Stav spojeni a odber. **Bez seznamu udalosti.**
|
||||
*
|
||||
* Seznam se meni pri kazde udalosti a kdyz byl v teze hodnote kontextu, prekreslil
|
||||
* se pri kazde udalosti kazdy, kdo si jen zaridil odber - tedy vsechny dotazy
|
||||
* s `refetchOn` na cele strance, i kdyz o ten typ udalosti nestaly. Pri behu
|
||||
* automatizace to znamenalo prekreslovani celeho dashboardu nekolikrat za sekundu.
|
||||
*/
|
||||
interface EventStreamState {
|
||||
status: StreamStatus;
|
||||
/** Poslednich par udalosti, nejnovejsi prvni. */
|
||||
events: DashboardEvent[];
|
||||
/** Prihlaseni k odberu. Vraci funkci pro odhlaseni. */
|
||||
subscribe: (listener: (event: DashboardEvent) => void) => () => void;
|
||||
}
|
||||
@@ -15,13 +21,15 @@ const noop = () => () => {};
|
||||
|
||||
const EventStreamContext = createContext<EventStreamState>({
|
||||
status: 'closed',
|
||||
events: [],
|
||||
subscribe: () => {
|
||||
console.warn('[stream] subscribe mimo EventStreamProvider, udalosti nedorazi');
|
||||
return noop();
|
||||
},
|
||||
});
|
||||
|
||||
/** Poslednich par udalosti, nejnovejsi prvni. Zvlast, protoze se meni porad. */
|
||||
const EventLogContext = createContext<DashboardEvent[]>([]);
|
||||
|
||||
const MAX_KEPT = 30;
|
||||
|
||||
/**
|
||||
@@ -59,18 +67,29 @@ export function EventStreamProvider({ children }: { children: ReactNode }) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const value = useMemo<EventStreamState>(
|
||||
() => ({ status, events, subscribe }),
|
||||
[status, events, subscribe],
|
||||
);
|
||||
const value = useMemo<EventStreamState>(() => ({ status, subscribe }), [status, subscribe]);
|
||||
|
||||
return <EventStreamContext.Provider value={value}>{children}</EventStreamContext.Provider>;
|
||||
return (
|
||||
<EventStreamContext.Provider value={value}>
|
||||
<EventLogContext.Provider value={events}>{children}</EventLogContext.Provider>
|
||||
</EventStreamContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useEventStream(): EventStreamState {
|
||||
return useContext(EventStreamContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posledni udalosti. Bere si je jen ten, kdo je opravdu vypisuje.
|
||||
*
|
||||
* Kdo chce jen reagovat na udalost, pouzije `useEventListener` - ten se
|
||||
* prekresluje jen kdyz sam neco zmeni, ne pri kazde udalosti na strance.
|
||||
*/
|
||||
export function useEventLog(): DashboardEvent[] {
|
||||
return useContext(EventLogContext);
|
||||
}
|
||||
|
||||
/** Zavola callback jen pri udalostech uvedenych typu. */
|
||||
export function useEventListener(
|
||||
types: DashboardEventType[],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AlarmClock, Bell, CheckCircle2, LifeBuoy, UserCheck, Webhook, Workflow, X } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { useEventStream } from '@/components/dashboard/EventStreamProvider';
|
||||
import { useEventLog } from '@/components/dashboard/EventStreamProvider';
|
||||
import { cn } from '@/lib/cn';
|
||||
import type { DashboardEvent, DashboardEventType } from '@/types/events';
|
||||
|
||||
@@ -27,7 +27,7 @@ const style: Record<DashboardEventType, { icon: LucideIcon; tone: string }> = {
|
||||
|
||||
/** Bubliny o tom, co se prave stalo. Bez nich by zive zmeny nebyly poznat. */
|
||||
export function EventToasts() {
|
||||
const { events } = useEventStream();
|
||||
const events = useEventLog();
|
||||
const [visible, setVisible] = useState<DashboardEvent[]>([]);
|
||||
const [dismissed, setDismissed] = useState<Set<string>>(new Set());
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export function CallToAction() {
|
||||
<div className="relative overflow-hidden rounded-3xl border border-brand-400/25 bg-gradient-to-br from-brand-500/12 via-ink-800/60 to-accent-500/12 px-6 py-14 text-center sm:px-14">
|
||||
<div
|
||||
aria-hidden
|
||||
className="animate-pulse-slow pointer-events-none absolute -top-24 left-1/2 size-96 -translate-x-1/2 rounded-full bg-brand-500/20 blur-[100px]"
|
||||
className="pointer-events-none absolute -top-24 left-1/2 size-96 -translate-x-1/2 rounded-full bg-brand-500/20 blur-[100px]"
|
||||
/>
|
||||
<h2 className="relative text-3xl font-bold text-white sm:text-4xl">
|
||||
{t('cta.titleFirst')} <span className="text-gradient">{t('cta.titleAccent')}</span>
|
||||
|
||||
+28
-2
@@ -174,11 +174,21 @@
|
||||
Vlastni utility. Pouzivat jen pro veci, ktere se opakuji vsude.
|
||||
------------------------------------------------------------------- */
|
||||
|
||||
/* Sklenena karta - zaklad vetsiny panelu na webu i v dashboardu */
|
||||
/*
|
||||
* Sklenena karta - zaklad vetsiny panelu na webu i v dashboardu.
|
||||
*
|
||||
* **Bez `backdrop-filter`.** Bylo tu `blur(14px)`, jenze pod kartami je plna
|
||||
* barva (`bg-ink-950` na dashboardu), takze se rozmazavala jednolita plocha:
|
||||
* zadny rozdil videt nebyl a kazda karta si za to drzela vlastni rozmazanou
|
||||
* vrstvu ve skladaci. Pri padesati kartach a zivem streamu udalosti to
|
||||
* vytizilo GPU natolik, ze vedle toho sekalo prehravani videa.
|
||||
*
|
||||
* Rozmazani zustava tam, kde neco opravdu prekryva: lepici hlavicka
|
||||
* a podklady modalu maji svoje `backdrop-blur-*` primo u sebe.
|
||||
*/
|
||||
@utility glass {
|
||||
background-color: --alpha(var(--color-ink-800) / 70%);
|
||||
border: 1px solid --alpha(var(--color-ink-600) / 70%);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -209,3 +219,19 @@
|
||||
@utility mask-fade-b {
|
||||
mask-image: linear-gradient(to bottom, black, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Kdo si v systemu vypnul animace, nema je dostat ani tady.
|
||||
*
|
||||
* Nekonecne animace jsou to jedine, co na klidne strance porad zamestnava
|
||||
* skladac - a prave ty jsou tady zbytne.
|
||||
*/
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ export default function Login() {
|
||||
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-ink-900 px-5 py-14">
|
||||
<div aria-hidden className="pointer-events-none absolute inset-0 -z-10">
|
||||
<div className="absolute inset-0 bg-grid opacity-25" />
|
||||
<div className="animate-pulse-slow absolute -top-32 left-1/2 size-[34rem] -translate-x-1/2 rounded-full bg-brand-500/16 blur-[120px]" />
|
||||
<div className="animate-pulse-slow absolute -bottom-40 right-0 size-[26rem] rounded-full bg-accent-500/14 blur-[120px]" />
|
||||
<div className="absolute -top-32 left-1/2 size-[34rem] -translate-x-1/2 rounded-full bg-brand-500/16 blur-[120px]" />
|
||||
<div className="absolute -bottom-40 right-0 size-[26rem] rounded-full bg-accent-500/14 blur-[120px]" />
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-md">
|
||||
|
||||
@@ -121,7 +121,10 @@ export default function Tickets() {
|
||||
if (channel) params.set('channel', channel);
|
||||
const search = params.toString();
|
||||
return `/api/dashboard/tickets${search ? `?${search}` : ''}`;
|
||||
}, [scope, tenantId, assignee, status, channel]);
|
||||
// Vsechno, co se do adresy dosazuje, musi byt i tady. Chybel `groupId`
|
||||
// a spol., takze prepnuti sekce adresu neprepocitalo a seznam se nenacetl
|
||||
// znovu - filtr vypadal, ze nefunguje.
|
||||
}, [scope, tenantId, assignee, status, channel, typeId, tag, stage, groupId]);
|
||||
|
||||
const workloadPath = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
Reference in New Issue
Block a user