Case study · käyttöliittymäsuunnittelu ja toteutus
PARKSIDE-käyttöohjeetKatalogi, jota ei ollut olemassa
Lidlin PARKSIDE-työkaluista ei ole julkista luetteloa. Manuaalin löytää vain jos
tietää tuotenumeronsa etukäteen. Rakensin puuttuvan katalogin ja sen käyttöliittymän:
10 257 listausta kolmelta markkinalta, täydennettynä niillä tiedoilla joita työkalun
omistaja oikeasti kysyy. Yksi staattinen sivu, ei kehystä eikä käännösvaihetta.
Rooli suunnittelu + toteutusKesto 7 työpäivää, 88 committiaPino HTML · CSS · vanilla JS · PythonJulkaisu staattinen, 139 MB
≤ 3tulosta kyselyä kohden. Ei sivutusta eikä listausreittiä.
Rajapinta, jota ei voi selata
Lidlin manuaalihaku palauttaa enintään kolme tulosta
kyselyä kohden. Sivutusparametrit se jättää huomiotta, eikä listausreittiä ole lainkaan.
Tämä ratkaisi aika pitkälti koko suunnittelun. Haku olettaa että tiedät jo mitä
etsit: syötä tuotenumero, saat manuaalin. Mutta jos kysymys on mitä tuotteita
ylipäätään on, mikä näistä on uusin tai mikä akku tähän käy, siihen ei
pysty vastaamaan millään. Ei ole mitään mitä selata.
Sama rajoite antoi myös keinon saada katalogi ulos. Haku täsmää etuliitteisiin, myös
tuotenumerossa: 4687 löytää numeron 46876, mutta 6876
ei löydä mitään, koska pääte ei ole etuliite. Jokaisella manuaalilla on numeerinen IAN,
joten koko luettelon saa käytyä läpi rekursoimalla numeroetuliitteitä. Kysely joka
palauttaa enintään kolme on valmis. Tyhjä kysely katkaisee haaransa. Kaikki muut
pilkotaan kymmeneen pidempään kyselyyn. Aakkosittain sama ei onnistu, koska
läpikäynti näkee joka kerta vain ne samat kolme ensimmäistä.
Kaksi asiaa tästä kannattaa pitää mielessä loppusivun ajan, koska ne rajaavat
käyttöliittymää enemmän kuin mikään makuvalinta. Ensinnäkin lopputulos on koko joukko,
ei otos. Siksi laskurit saavat luvata täsmällisiä lukuja, eikä ruudukon tarvitse
varautua siihen että jotain jäisi piiloon. Toiseksi tuotenumero on ainoa kenttä johon
rajapinta oikeasti tarttuu, joten nimet ja luokat jouduin johtamaan itse manuaaleista.
Siitä seuraa myöhemmin sekin, että haku tunnistaa pelkät tuotenumerot ja ohittaa silloin
suodattimet (osio 5).
Käyttöliittymän tehtävä ei siis ollut nopeuttaa hakua vaan näyttää mitä on olemassa.
Siitä lähdin liikkeelle.
KUVA 1Sama data, kaksi pintaa. Rajapinta vastaa vain kysymykseen jonka vastauksen tiedät jo. Arkisto vastaa siihen mitä et osannut kysyä.
Kenelle
Käyttäjä on työkalun omistaja tai sellaista harkitseva. Käytännössä kuka tahansa 20 V akkuekosysteemissä, joka yrittää selvittää mikä sopii mihin. Samat kysymykset toistuivat:
Mikä akku tähän käy? Alustaa ei lue tuotteen nimestä. Ja 40 V ei ole oma alustansa vaan kaksi 20 V kennostoa.
Onko tämä uudempi kuin minun? Revisio (A1 → B2 → C4) kertoo sen, mutta vain saman mallin sisällä.
Mitä laatikossa on? Nimi ei kerro. “Akkuiskuporakonesarja” ei paljasta sisältyykö akku ja laturi vai ei.
Sivun tehtävä ei ole hakeminen. Sen tehtävä on näyttää mitä on olemassa.
2Tietomalli
Kolme identiteettiäja se oikea riippuu siitä mitä kysytään.
Mikä on yksi rivi ruudulla?
Ennen kuin ruudukkoa voi piirtää, pitää päättää mitä yksi kortti tarkoittaa.
Datassa on kolme eri identiteettiä, ja pienin niistä on alle kolmasosa suurimmasta.
Yksikkö
Määrä
Mitä se on
Vastaa kysymykseen
Listaus
10 257
Yksi tuote yhdellä markkinalla
Myydäänkö tätä Suomessa?
Tuotenumero
4 806
IAN — yksi pakkaus
Mikä manuaali minun laatikkooni kuuluu?
Tuote
2 935
Malli + revisio
Mitä työkaluja on olemassa?
Sama akku esiintyy 29 eri tuotenumerolla: paljaana, laturin kanssa, osana sarjaa.
Ja jokaisella markkinalla on vielä oma listauksensa. Jos kortti olisi listaus, sama työkalu
täyttäisi ruudun kolmeen kertaan. Jos kortti olisi tuotenumero, akkuja olisi 29.
Valitsin siis, että kortti on tuote. Yksi kortti purkaa molemmat
päällekkäisyydet kerralla. Mutta yhdistäminen on aina väite jostakin, joten kortin pitää
kertoa myös se mitä se ei väitä.
KUVA 2Kortin anatomia. Jokainen elementti vastaa yhteen kysymykseen ylemmästä listasta. Mikään ei ole koristetta.
Ryhmittely ei ole puhdas sääntö, enkä saanut siitä sellaista. Mallikoodi ratkaisee
valtaosan, mutta viisi poikkeusta jouduin kirjaamaan käsin: kaksi listausta yhdistin
vaikka mikään koodi ei liitä niitä toisiinsa, yhdeksän pidin erillään vaikka koodi on
sama (PARS 4 A1 on sekä auton puhdistussetti että 4 V ruuvinväännin),
lisävarusteet sain omille korteilleen, monen työkalun sarjat purin työkaluikseen, ja
malli piti tunnistaa yli markkinarajojen. Käytännössä tuote on siis malli ja revisio,
paitsi silloin kun olen itse sanonut toisin.
Miksi sivulla on kaksi eri lukua
Arkistossa on 2 935 tuotetta, mutta ruudukko piirtää
2 932 korttia. Ero tulee 24 tuotenumerosta, joiden PDF on kadonnut
lähteestä. Ne ovat yhä rekisterissä, mutta piilossa oman suodattimensa takana kunnes
niitä erikseen pyytää. Kumpikin luku on oikein, koska “mitä arkisto sisältää” ja “mitä
ruudulla näkyy” ovat kaksi eri kysymystä. Yhtenä lukuna esitettynä toinen niistä
valehtelisi.
Kolmen sanan ero
Merkissä lukee “3 IAN” ja se avautuu muotoon sama malli ja revisio.
Ei “sama tuote”. Ero kuulostaa pilkunviilaukselta, mutta näiden kolmen listauksen
manuaalit eroavat toisistaan satoja sivuja, ja yksi PAP 20 B1 -akun 29
tuotenumerosta on oikeasti akku + laturi -paketti. Sanamuoto sai siis luvata vain sen
verran kuin data kantaa.
3Rautalanka
760 / 600 / 420 pxKolme katkaisukohtaa, jokainen korjaamassa omaa vikaansa.
Layout
Yksi näkymä, ei reittejä. Suodattimet ylhäällä pysyvinä riveinä, ruudukko niiden alla.
Kysymys “mitä on olemassa” vaatii, että vaihtoehdot näkyvät samaan aikaan tuloksen kanssa.
KUVA 3Työpöytä. Suodatinrivit ovat kaksisarakkeinen ruudukko: nimiö 96 px:n sarakkeessa, suodatinnapit viereisessä. Sarakeleveyden määrää pisin suomenkielinen nimiö, ei englanninkielinen.KUVA 4Kapea näyttö. Suodatinrivit veivät 868 px korkeutta 390 px:n ruudulla, eli enemmän kuin koko näyttö. Ne taittuvat painikkeen taakse, ja painike kertoo montako valintaa on päällä.
4Päätökset
Jokainen päätöskertoo myös mitä hylkäsin ja mitä valinta maksoi.
Yhdeksän päätöstä
Päätös josta puuttuu hylätty vaihtoehto on oikeastaan vain mielipide. Kirjasin nämä
siinä muodossa, että vuoden päästä näen itsekin mitä mittasin ennen kuin valitsin.
Ei kehystä
Arkkitehtuuri
Yksi HTML-tiedosto, joka lukee yhden JSON-tiedoston. Ei Reactia, Nextiä, Tailwindia eikä käännösvaihetta.
ValittuSivu on 62 kB (17 kB pakattuna) ja DOMContentLoaded 44 ms. Ruudukko rakentaa ensin 48 korttia ja kasvaa vierittäessä, joten pääsäie ei jää kiinni koko arkistoon.
HylättyReact + ReactDOM ovat yksinään ~45 kB pakattuna, eli noin kolme kertaa koko nykyisen sivun verran. Eikä mikään niiden tarjoamasta (SSR, reititys, API-reitit) koske yhtä sivua joka lukee yhden JSONin.
HylättyTailwindin CDN-versio on ~90 kB ja kääntää selaimessa. CLI taas toisi koko Node-työkaluketjun projektiin, jossa työnkulku on “muokkaa tiedostoa, lataa välilehti uudelleen”.
AiheuttaaAinoa asia jota oikeasti jäin kaipaamaan oli, ettei URL tunnistanut tuotetta. Sekin ratkesi noin 20 rivillä location.hash-käsittelyä.
Kortti on tuote, ei manuaali
Tietomalli
Yksi kortti = yksi malli + yksi revisio. Kortti yhdistää sekä markkinoiden että tuotenumeroiden päällekkäisyyden.
ValittuKielikohtaiset manuaalit tulevat korttiin painikkeina. Useampi tuotenumero tiivistyy “N IAN” -merkiksi, ja sen klikkaus listaa numerot hakukenttään vertailtavaksi.
HylättyKortti per listaus: sama työkalu täyttäisi ruudun kolmeen kertaan. Kortti per tuotenumero: yksi akku esiintyisi 29 kertaa.
AiheuttaaSama mallikoodi ei aina tarkoita samaa tuotetta, koska koodeja käytetään uudelleen. Siksi lisäsin “ei tämä tuote” -merkinnän, joka erottaa listauksen omaksi kortikseen ja jonka voi perua.
Revisioita ei yhdistetä oletuksena
Tietomalli
Malli PABS 20-Li kulkee revisiosta A1 (2015) revisioon I9 (2025). Jokainen on oma korttinsa.
ValittuKortti näyttää mallinsa revisiolinjan, ja markkinalta puuttuvat revisiot piirretään katkoviivalla eikä piiloteta. Suomi otti tuosta koneesta A1:n, C3:n, D5:n, E6:n, G8:n ja I9:n, muttei B2:ta, D4:ää, F7:ää eikä H9:ää. Aukot kertovat siinä yhtä paljon kuin täytöt.
ValittuRevision klikkaus rakentaa saman kortin uudelleen paikallaan ja ohittaa aktiiviset suodattimet. Jos pyydät revisiota nimeltä, tarkoitat sitä.
AiheuttaaRuudukko näyttää epäjohdonmukaiselta, kun yksi malli täyttää 8 korttia ja toinen yhden. Kyse on kattavuudesta eikä sekavuudesta, mutta koska sitä ei näe kortista, lisäsin “Yhdistä revisiot” -valinnan: 1 105 korttia → 756.
Fasettiluvut lasketaan muiden suodattimien mukaan
Fasetit
Napin luku kertoo mitä tämä valinta tuottaisi silloin, kun kaikki muut suodattimet ovat voimassa.
ValittuFasetti ei koskaan nollaa omia vaihtoehtojaan. Vaikka “20 V akku” on valittuna, näet yhä montako niitä olisi 12 V:llä. Vaihtaminen on yhden klikkauksen päässä.
ValittuLuvut lasketaan tuotteina, siis samassa yksikössä jonka ruudukko piirtää. Listauksina laskettuna 20 V akun nappi lupaisi 1 944 korttia, ja ruudukko piirtäisi niistä 438.
HylättyNollanapin himmentäminen opasiteetilla. Brändivihreällä otsakkeella tausta paistoi läpi, ja nappi näytti toisenlaiselta napilta eikä himmennetyltä. Nyt himmennän sisällön, en nappia.
Nimi luetaan manuaalista, ei katalogista
Sisältö
Katalogin nimikenttä on lyhennetty kentän mittaan ja katkeaa kesken sanan. Korvasin 6 781 nimeä manuaalin kannesta luetulla.
Katalogi sanoi
Kortti sanoo
IAN
Bensiinik. ketjusaha KAT
Bensiinikäyttöinen ketjusaha
495510
Akkuk. ruohonleikkuri i akkua
40 V Akkuruohonleikkuri
507920
Akkuk. 3 in 1 -yleispuhallin
12 V Akkukäyttöinen pieni yleispuhallin 3 in 1
504168
Αθόρ.αεροσυμπ.
Silent air compressor
339048
ValittuAlkuperäinen nimi jää talteen ja näkyy hoverilla, koska korjaus ei saa hävittää sitä mitä lähteessä luki.
HylättyEnglanninkielisen otsikon lukeminen PDF:stä automaattisesti. Kannet vaihtelevat liikaa: kokeilu tuotti saksankielisen otsikon, kaksi “User manual” -riviä ja kompressorille nimen “HOSE REEL”.
Järjestysvalikko on natiivi <select>
Saavutettavuus
Suljetun tilan tyylittelin itse, nuolta myöten, koska selaimen oma nuoli osuu laatikon reunaan. Avattu lista tulee käyttöjärjestelmältä eikä sitä voi tyylitellä.
HylättyOma listbox. Se olisi maksanut iOS:n rullavalitsimen, Androidin alapaneelin, näppäimistön type-aheadin ja ruudunlukijasemantiikan. Natiivilla ne tulevat kaikki ilmaiseksi.
AiheuttaamacOS asemoi popupin hieman sivuun, koska se sijoittaa valitun rivin kontrollin päälle. Päätin elää sen kanssa.
URL on hakukentän sisältö
Navigointi
Osoitteen hash on kirjaimellisesti se mitä hakukentässä lukee. #313266 avaa tuotteen ja #313266,495965 kahden vertailun.
ValittuVain haku koodataan, suodattimia ei. Suodattimilla katsellaan ympärille, haulla nimetään se mitä oltiin hakemassa, ja vain jälkimmäinen kannattaa jakaa eteenpäin.
ValittuOsoite päivittyy hiljaa, jättämättä merkintää selaimen historiaan. Hakukenttään kirjoittaminen ei ole siirtymistä sivulta toiselle, ja muuten yksi haku täyttäisi paluunapin kymmenellä välivaiheella (replaceState, ei pushState).
Kapealla suodattimet taittuvat, mutta laskuri jää näkyviin
Responsiivisuus
Kolme katkaisukohtaa (760 / 600 / 420 px). Jokainen niistä syntyi oikeasta viasta, ei viimeistelystä.
ValittuOtsake on tarttuva vain leveällä ruudulla. Täydessä korkeudessaan se peitti 750 px 813 px:n puhelinruudusta. Alle 760 px:n se on staattinen ja suodattimet vierivät pois tieltä.
ValittuSuodatinrivit veivät 868 px korkeutta 390 px:n ruudulla, joten ne menevät “Suodattimet”-painikkeen taakse. Painike näyttää valintojen määrän, koska rivien ollessa piilossa suodatettu näkymä näyttäisi muuten aivan samalta kuin suodattamaton.
AiheuttaaSuodatinnapit työnsivät sarakkeensa leveämmäksi kuin puhelimen ruutu, jolloin koko sivu alkoi vieriä sivusuunnassa sen sijaan että napit olisivat siirtyneet uudelle riville. Syy oli yksi CSS:n oletusarvo ja korjaus yksi rivi: min-width: 0.
Julkaisu on datamuunnos, ei sivun haara
Julkaisu
Sama index.html toimii sekä paikallisena työkaluna että julkaistuna sivuna. Ei ehtolauseita eikä build-lippuja.
ValittuMuokkauskontrollit kytkeytyvät päälle vain jos tarkistuspalvelin vastaa. Kun sitä ei ole, ne piilottavat itsensä, joten julkaistulle sivulle ei jää kuollutta käyttöliittymää.
ValittuPDF-linkki putoaa takaisin valmistajan omaan osoitteeseen, kun paikallista tiedostoa ei ole. Siksi julkaisu on 139 MB kansikuvia eikä 19 GB manuaaleja, eikä se levitä yhtäkään manuaalia edelleen.
AiheuttaaLinkki kuolee, jos Lidl poistaa tiedoston. Kahdeksan on jo kuollut. Ne jäävät rekisteriin näkyviin oman suodattimensa taakse.
5Suodatus
Kuusi riviäjoista jokainen vastaa yhteen kysymykseen. Ruudukko vain piirtää tuloksen.
Suodatus on sivun varsinainen käyttöliittymä
Ruudukko ei tee päätöksiä. Se vain piirtää sen mitä jää jäljelle. Kaikki kiinnostava
tapahtuu suodatinriveillä, ja niihin meni myös selvästi enemmän aikaa kuin mihinkään
muuhun osaan sivua.
Kuusi riviä, kussakin yksi kysymys
Rivi ei ole datakenttä vaan kysymys. Saman tiedon olisi voinut jakaa monella tapaa,
mutta jaoin sen sen mukaan mitä ihmiset oikeasti tulevat kysymään.
Rivi
Kysymys jonka se esittää
Esimerkkejä
Markkina
Onko tätä myyty täällä?
FI · EN · DE
Käyttövoima
Mitä minun täytyy omistaa, jotta tämä toimii?
20 V akku · verkkovirta · bensiini
Pakkaus
Mitä laatikossa on?
akku · laturi · sarja · pelkkä työkalu
Sarja
Onko tämä PERFORMANCE vai erikoiserä?
PERFORMANCE · limited
Revisio
Monesko versio tästä mallista?
A · B · C · …
Vuosi
Miltä vuodelta manuaali on?
2015 … 2026 · ei vuotta
Kannattaa katsoa Käyttövoima-rivin sanamuotoa. Se ei kysy “mikä tämän tekniikka
on” vaan “mitä minun täytyy omistaa”. Siksi 40 V on oma nappinsa vaikkei se ole oma
alustansa. Se on kaksi 20 V kennostoa, ja nappi sanoo sen ääneen. Samasta syystä
painepesu ja puutarha ovat ryhmiä eivätkä käyttövoimia.
Rivin sisällä TAI, rivien välillä JA
Yhden rivin napit ovat vaihtoehtoja samaan kysymykseen, joten useampi valinta laajentaa
tulosta. Eri rivien valinnat ovat eri kysymyksiä, joten ne kaventavat sitä. Tämä on ainoa
sääntö joka käyttäjän täytyy oppia, eikä sitä kerrota sivulla missään. Sen oppii
katsomalla mitä luvut tekevät.
KUVA 5Sääntöä ei selitetä käyttäjälle missään. Se opitaan siitä että luvut käyttäytyvät johdonmukaisesti: kun lisää napin samalle riville, tulos kasvaa. Uudelle riville lisätty nappi pienentää sitä.
Laskuri jättää oman rivinsä laskematta
Tämä on koko suodatuksen tärkein yksityiskohta ja samalla helpoin tehdä väärin. Kun
lasken mitä Käyttövoima-rivin nappi tuottaisi, kaikki muut rivit ovat voimassa,
mutta käyttövoimavalintaa itseään en huomioi. Muuten yhden napin valitseminen nollaisi
saman rivin muut vaihtoehdot, ja käyttäjä olisi umpikujassa josta pääsee vain
perumalla valintansa.
Mitkä ehdot ovat voimassa, kun kunkin rivin nappien luvut lasketaan
Laskettava rivi
Markk.
Käyttöv.
Pakk.
Sarja
Rev.
Vuosi
Haku
Markkina
—
✓
✓
✓
✓
✓
✓
Käyttövoima
✓
—
✓
✓
✓
✓
✓
Pakkaus
✓
✓
—
✓
✓
✓
✓
Sarja
✓
✓
✓
—
✓
✓
✓
Revisio
✓
✓
✓
✓
—
✓
✓
Vuosi
✓
✓
✓
✓
✓
—
✓
KUVA 6Tyhjä lävistäjä on koko sääntö: jokainen rivi laskee itsensä ulos. Haku sen sijaan on aina voimassa, koska se ei ole yksi riveistä vaan rajaa niitä kaikkia.
Kun nappi ja ruudukko laskevat eri yksikköä
Nappi vastasi eri kysymykseen kuin siltä oli kysytty: se laski listauksia, ruudukko
piirsi tuotteita. Nyt luku lasketaan samassa yksikössä kuin ruudukko piirtää.
Tulosrivillä näkyy silti molemmat, montako tuotetta näkyy ja montako manuaalia niiden
takana on, koska jälkimmäinen kertoo mitä arkisto oikeasti sisältää.
Neljä kohtaa, joissa rikoin symmetrian
Puhdas fasettimalli olisi siistimpi, mutta se antaisi väärän kuvan datasta. Tein siis
neljä poikkeusta tarkoituksella:
Monijännitteinen työkalu näkyy jokaisessa perheessä jonka se hyväksyy. 20 V radio, joka käy myös 12 V kennostolla, löytyy kummallakin napilla eikä vain napilla “monijännitteinen”. Kysymykseen “mitä minun täytyy omistaa” on tässä kaksi oikeaa vastausta.
“Ei vuotta” istuu vuosirivissä eikä erillisenä valintana. Sen voi pyytää vuoden 2019 rinnalla ja saa molemmat, koska vuosinappi on TAI ja vuoden puuttuminen on vain yksi vastaus lisää samaan kysymykseen.
Ryhmäliput eivät ole poissulkevia.Puutarha, painepesu ja kompressori kertovat mihin työkalu on tarkoitettu. Ne eivät väitä, että kyseessä olisi jotain muuta kuin työkalu, joten ne eivät kilpaile pakkaustyyppien kanssa vaan elävät niiden rinnalla.
“Tarvitsee tarkistuksen” on oma nappinsa käyttövoimarivissä, vaikkei se ole käyttövoima. Se kertoo tiedon laadusta eikä tuotteesta, mutta kuuluu silti samaan riviin: se on vastaus kysymykseen “mikä tätä käyttää”, ja vastaus kuuluu “en tiedä varmasti”.
Haku ei ole suodatin
Hakukenttä ja suodatinrivit näyttävät samalta työkalulta, mutta käyttäytyvät eri tavalla.
Ero on tarkoituksellinen: suodatin rajaa, haku nimeää.
Pelkistä tuotenumeroista koostuva haku ohittaa suodattimet. Jos kirjoitat numeroita jotka arkisto tuntee, saat täsmälleen ne, vaikka aktiivinen suodatin sanoisi muuta. Numeron kirjoittaminen on selvempi aikomus kuin selailu.
Jokaisen osan on oltava tunnettu tuotenumero. Muuten 2015 tulkittaisiin ohitukseksi eikä vuosiluvuksi, jonka joku kirjoitti. Arkiston oma numeroalue 35391–550492 ratkaisee kummasta on kyse, joten sääntö tulee datasta eikä arvauksesta.
Kaksi tai useampi tuotenumero tarkoittaa vertailua. Kortit pidetään silloin tarkoituksella erillään sen sijaan että ne ryhmiteltäisiin, koska useamman numeron kirjoittaminen on pyyntö nähdä ne rinnakkain.
Mallikoodit ovat hakukelpoista tekstiä. Suomenkielinen nimi on usein pelkkä “Akkuporakone”, joten koodi on ainoa tapa pyytää tiettyä työkalua. Ja koska sarja nimeää osansa, akku + laturi -paketti löytyy myös laturin koodilla.
Vähintään kaksi merkkiä. Yksi merkki osuu tuhansiin tuotteisiin eikä kerro mitään siitä mitä haettiin. Sama raja on alkuperäisessä rajapinnassakin, ja samasta syystä.
Poissaolo on tuotteen ominaisuus eikä listauksen
“Vain ilman manuaalia olevat” kuulosti yksinkertaiselta ehdolta ja osoittautui
kolmiportaiseksi. IAN 300098 menetti saksalaisen tiedostonsa, mutta suomalainen ja
englantilainen ovat levyllä, joten se ei ole tuote ilman manuaalia. Eikä tarkistus pysähdy
siihen: jos saman tuotteen toinen listaus tarjoaa saman dokumentin, se on jo
samalla kortilla. Tuote on ilman manuaalia vasta kun yksikään sen listaus ei tarjoa
sellaista.
Puoli miljoonaa kutsua per näppäinpainallus
Riveillä on noin viisikymmentä nappia, ja jokainen niistä käy koko aineiston läpi.
Tuoteavaimen selvittäminen ei ole ilmaista, koska se joutuu ratkaisemaan mallin yli
markkinarajojen. Nappikohtaisesti laskettuna sitä kutsuttaisiin puoli miljoonaa
kertaa jokaisella näppäinpainalluksella. Siksi avaimet muistetaan yhden
laskentakierroksen ajan, mutta ei sen pidempään: muuten mallin, ryhmittelyn tai
“Yhdistä revisiot” -valinnan muuttaminen jättäisi vanhentuneen avaimen elämään.
Paljonko sääntöjä tässä lopulta on
Kuudella rivillä on yhteensä 56 nappia, ja niiden lisäksi neljä
kytkintä: yhdistä revisiot, vain limited-erät, vain uniikit ja saatavuus. Jokainen
niistä on päällä tai pois, joten käyttäjä voi olla 2⁶⁰ eri tilassa.
Se on noin 1,15 triljoonaa yhdistelmää, eikä yhdessäkään niistä saa näkyä ruudulla
väärää lukua.
Rivi
Nappeja
Mistä ne tulevat
Markkina
3
DE · EN · FI
Käyttövoima
15
14 käyttövoimaa + “tarvitsee tarkistuksen”
Pakkaus
10
sisältö, ryhmäliput ja “ei mitään näistä”
Sarja
2
perus · PERFORMANCE
Revisio
9
A–I, mallilinjan pituus datassa
Vuosi
17
2011–2026 + “ei vuotta”
Sääntöjä on silti vähän. Koko suodatus on yksitoista pientä sääntöä ja
59 riviä koodia: kieli, käyttövoima, sarja, pakkaus, tyyppi, revisio,
vuosi, haku, saatavuus, limited ja uniikit. Rivi kysyy yhden asian, yksi sääntö vastaa
siihen, ja rivien välinen JA ketjuttaa ne.
Se pysyy pienenä, koska monimutkaisuus istuu kysymyksissä eikä koodissa. Ainoat kohdat
joissa sääntö tarvitsee muutakin kuin “kuuluuko tuote tähän arvoon” ovat ne neljä
poikkeusta jotka luettelin yllä. Kaikki muu on samaa kaavaa.
Lasku tulee toisaalta. Jokainen muutos laskee kaikkien 56 napin luvut uudelleen, ja
koska luku lasketaan koko aineistosta, se on 574 392 tarkistusta.
Siitä syntyy updateCounts(), joka on osion 6 mittausten jälkeen sivun
suurin yksittäinen nykäys.
Kokeile itse
Alla on sama logiikka 18 aidolla suomalaisella listauksella. Vaihda laskutapaa oikealta
ja katso mitä väärä laskutapa tekee saman rivin muille vaihtoehdoille.
Käyttövoima
Pakkaus
Ryhmä
Vähintään 2 merkkiä. Pelkät tunnetut tuotenumerot ohittavat suodattimet.
18 tuotetta · 70 tuotenumeroa · ei suodattimia
Yliviivattu rivi kortissa on katalogin alkuperäinen nimi. Oikeassa arkistossa se näkyy hoverilla.
6Suorituskyky
Mitattueikä arvioitu. Jokainen luku on selaimen profiloijasta.
Kolmetuhatta korttia ilman kehystä
Sivu ei valitse oletusmarkkinaa, joten ensimmäinen näkymä on koko arkisto: 2 932
tuotetta. Suomen katalogi on vain pienempi tapaus (1 105), ei oletus. Kehyksettömyys ei
poista suorituskykytyötä mihinkään, se vain siirtää sen minun tehtäväkseni.
Yksi oire, kaksi eri syytä
Puhelimella ensimmäinen painallus tuntui katoavan. Oletin että ruudukko pitää pääsäiettä
varattuna, ja se pitikin osittain paikkansa. Loput selvisivät vasta profiloimalla:
oireita oli yksi, syitä useampi, eikä se jonka korjasin ensimmäisenä ollut niistä
suurin.
Ruudukkoa ei voinut keskeyttää. Erän koko mitataan nyt ajassa eikä korttimääränä, jolloin hidas puhelin saa pienemmän erän kuin työpöytä ilman että kumpaakaan säädetään käsin. Ja pointerdown pysäyttää täytön, jotta kosketus saa vastauksen heti eikä jonon perässä.
Käynnistys oli aidosti hidas, muttei siitä syystä kuin luulin. 5,7 MB:n JSON.parse vie 28 ms, eli se ei ollut ongelma lainkaan. Sen sijaan kaksi apufunktiota kävi läpi kaikki 10 257 listausta jokaista korttia kohden kysyäkseen mitä muuta samalla tuotenumerolla on. Yhdestä 1 104 ms:n tehtävästä ne veivät 945 ms ja 620 ms. Nyt ne lukevat indeksiä.
Kosketuksen vasteaika puhelimella
Ennen
1 600 ms
Jälkeen
99 ms
Teeman vaihto: tyylien uudelleenlaskenta
Ennen
131 ms
Jälkeen
17 ms
Raskain yksittäinen käynnistystehtävä
Ennen
1 104 ms
Jälkeen
375 ms
2 935 tuotteen lajittelu nimen mukaan
Ennen
155 ms
Jälkeen
2 ms
Yksi näppäinpainallus hakukentässä
Ennen
40 ms
Jälkeen
10 ms
Ensimmäisen täytön kokonaistehtävä, puhelin
Ennen
2 215 ms
Jälkeen
878 ms
Kolmas kierros: paloittelu oli hoitanut väärää asiaa
Nuo kaksi korjausta eivät riittäneet. Puhelin jäätyi yhä, jos avasi asetusvalikon kesken
ensimmäistä täyttöä, ja palautui itsestään juuri kun täyttö valmistui. Se ajoitus nimesi
syyllisen, koska sen jälkeen ei rakenneta enää mitään.
Paloittelu oli levittänyt kustannuksen useaan tehtävään mutta ei vähentänyt sitä. Kyse ei
ollut työn ajoituksesta vaan määrästä: puhelimessa näkyy noin kaksi korttia, ja sivu
rakensi 2 932 korttia ja 87 636 DOM-solmua ennen
kuin vastasi mihinkään.
Nyt täyttö pysähtyy 48 korttiin. Ruudukon alle jää sentinel, jota
IntersectionObserver tarkkailee 1 400 px:n ennakolla ja joka pyytää 96
korttia lisää ennen kuin lukija ehtii perille. Yhteenvetorivi kertoo yhä todellisen
kokonaismäärän: mitään ei piiloteta, vain DOM on lykätty. Ensimmäinen täyttö on
48 korttia ja 1 674 solmua, ja skriptiaika putosi
974 ms:stä 371 ms:iin.
Miksi tätä ei löytynyt aiemmin
Mittalaite valehteli. DevToolsin emulaatio ja headless Chrome vaihtavat ruudun koon
mutta lainaavat työpöydän kompositorin, joten sivu mittasi puhtaana kolmessa
selaimessa samalla kun oikea puhelin jäätyi sekunneiksi. Vika löytyi vasta
profiloijalla, joka ajetaan itse laitteella.
Enkä väitä tätä valmiiksi. Jäljellä olevasta noin 450 ms:n blokkauksesta
updateCounts() on 230 ms ja ajaa yhtenä tehtävänä,
koska se käy jokaisen tietueen läpi kerran nappia kohden. Se on nyt suurin
yksittäinen nykäys.
Näkymän ulkopuoliset kortit eivät maksa mitään
Teeman vaihto ja hakukentän näppäinpainallus paranivat samasta muutoksesta:
content-visibility ja mitattu contain-intrinsic-size 430 px.
Ilman mitattua arviota vierityspalkki hyppii. Mitatun kanssa selain ohittaa näkymän
ulkopuolisten korttien tyylityksen ja asettelun kokonaan. Kansikuvat ladataan laiskasti,
joten ensimmäisellä maalauksella niitä haetaan 245 kappaletta 6 153:sta.
Kaksi asiaa, jotka näyttivät halvoilta mutta eivät olleet
Profilointi nosti esiin kaksi kohtaa, joita en olisi arvannut koodia lukemalla.
String.localeCompare rakentaa uuden vertailijan jokaista vertailua
kohden, joten 2 935 tuotteen lajittelu nimen mukaan maksoi 155 ms. Jaettuna
Intl.Collator-oliona sama on 2 ms. Ja fasettiluvut käyvät jokaisen
listauksen läpi kerran nappia kohden, yhteensä 164 ms.
Fasettiluvuissa korjaus ei ollut tehdä työtä nopeammin vaan tehdä se eri
järjestyksessä. Kukaan ei lue napin lukua sillä sekunnilla kun ensimmäiset kortit
vasta ilmestyvät, joten lasken luvut nyt ruudukon jälkeen enkä ennen sitä. Mitattu
kokonaisaika ei muuttunut lainkaan.
Miksi pidän tätä suunnittelukysymyksenä
1,6 sekunnin vaste kosketukseen ei tunnu hitaalta vaan rikkinäiseltä, ja käyttäjä
painaa uudelleen. Kaksi korjauksista ei nopeuta mitään mitattavaa kokonaisaikaa: erän
keskeytys ja fasettilukujen siirto vain asettavat työn siihen järjestykseen jossa
käyttäjä sitä katsoo. Se on käyttöliittymäpäätös eikä optimointi.
7Teemat
Väri on maaliaTeema ei muuta sitä, mitä tuotteita ruudulla näkyy.
Kolme teemaa, yksi token-joukko
Värit luin PARKSIDEn omista design-tokeneista heidän tyylitiedostostaan, en poiminut
niitä silmämääräisesti kuvakaappauksesta. Vihreä #1e4b32, power-vihreä
#00ff99, punainen #cd2828.
Plain #111111
20 V 1926verkko 3205ilma 463
20 V AkkuporakonePABS 20-Li I9 · 2025-02
Parkside #1e4b32
20 V 1926verkko 3205ilma 463
20 V AkkuporakonePABS 20-Li I9 · 2025-02
Performance #cd2828
20 V 1926verkko 3205ilma 463
20 V AkkuporakonePABS 20-Li I9 · 2025-02
Oletus: Parkside · valinta muistetaan localStoragessa
Kolme asiaa, jotka teemat pakottivat ratkaisemaan
Otsake on kokonaan brändivihreä, joten kaikki sen päällä tarvitsee oman kontrastinsa. --header-bg, --header-ink, --header-muted ja --header-line muodostavat oman token-joukkonsa, ja napit pitävät vaalean pintansa.
Valittu nappi ottaa power-vihreän#00ff99, koska brändivihreä brändivihreän päällä katoaisi kokonaan. Sama sääntö toistuu tälläkin sivulla, jonne lainasin valitun napin värin suoraan.
Teema asetetaan <head>:ssä ennen tyylitiedostoa. Muuten sivu välähtäisi joka käynnillä edellisen teeman värit ennen oikeita.
8Kieli
Kaksi eri asiaajotka menevät käyttäjillä jatkuvasti sekaisin.
Käyttöliittymän kieli ei ole katalogin kieli
Ylhäällä oikealla on FI / EN. Alempana suodatinriveissä on Markkina. Ne ovat eri asioita,
ja niiden yhdistäminen olisi ollut houkutteleva mutta väärä yksinkertaistus.
FI / EN vaihtaa sivun tekstit. Oletuksena on suomi vain jos selain pyytää suomea, muuten englanti.
Markkina valitsee selattavan katalogin. Suomenkielinen käyttöliittymä voi hyvin listata englantilaisia manuaaleja, ja se on täysin kelvollinen tilanne.
Englanti on lähdeteksti, suomi on päällyste. Puuttuva avain putoaa englantiin eikä näytä avaimen nimeä, joten käyttäjä ei koskaan näe facet.power.label-tyylistä vuotoa.
Kielenvaihto rakentaa napit uudelleen eikä vain nimeä niitä
Pelkkä uudelleennimeäminen olisi jättänyt valintatilan ja nimiön eri tahtiin. Nyt
nappirivit tyhjennetään ja rakennetaan uudelleen valintajoukosta, koska muuten jokaisesta
rivistä ilmestyisi toinen kappale. Ja suodatinsarake on 96 px leveä siksi, että
KÄYTTÖVOIMA on puolitoista kertaa POWER-sanan mittainen. Layout kannattaa
mitoittaa pisimmän kielen mukaan eikä lähdekielen.
9Ratkaisematta
KirjattuTunnettu puute on eri asia kuin tuntematon.
Mikä on yhä kesken
Arkisto on käytössä ja julkaistu, mutta kolme asiaa on yhä kesken. Kirjasin ne ylös,
koska tunnettu puute on eri asia kuin tuntematon.
Lidl poistaa manuaaleja, ja se jatkuu. Lähteestä on jo kadonnut
24 tuotenumeron PDF, ja sama voi tapahtua isoille tuotekuville.
Yksiselitteistä ratkaisua tähän ei ole: säilyttäminen ja uudelleenjulkaisu ovat eri
kysymyksiä, enkä ole vastannut jälkimmäiseen. Siksi pidän tässä vaiheessa kaiken
tallessa paikallisesti, 19 GB manuaaleja ja
127 MB täysikokoisia tuotekuvia, vaikka julkaistu sivu linkittää
manuaalit valmistajalle eikä levitä yhtäkään niistä.
Tuotekuvia on 1 356 / 2 935. Lopuissa näkyy manuaalin ensimmäinen sivu, joka on mitä sattuu olemaan. Neljää Lidlin verkkokauppaa (BE / PL / NL / ES) en ole vielä käynyt läpi.
Hyväksytty listaus ei enää parane säännöllä. Merkitsin kaikki 10 257 varmaksi, joten myöhemmin lisätty luokka ei tavoita niitä. Vain merkinnän poistaminen tavoittaa.