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 + toteutus Kesto 7 työpäivää, 88 committia Pino HTML · CSS · vanilla JS · Python Julkaisu staattinen, 139 MB
Avaa sivustops.grönlund.net
10 257listausta
3 markkinaa
2 935erillistä
tuotetta
62 kBsivun koko
17 kB pakattuna
44 msDOMContentLoaded
0riippuvuutta
ei build-vaihetta
2 001käsin tehtyä
korjausta
1Ongelma

≤ 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.

LIDLIN RAJAPINTA IAN 313266 3 osumaa · loput 10 254 näkymättömissä SELATTAVA ARKISTO …
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. “Akkuiskuporakone­sarja” 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 onVastaa kysymykseen
Listaus10 257Yksi tuote yhdellä markkinallaMyydäänkö tätä Suomessa?
Tuotenumero4 806IAN — yksi pakkausMikä manuaali minun laatikkooni kuuluu?
Tuote2 935Malli + revisioMitä 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ä.

20 V AKKU 3 IAN A1 B2 C3 D4 E6 I9 FI · PDF EN · PDF Käyttövoima luetaan manuaalista, ei nimestä. Yksi luokka 14:stä · todisteet jäävät talteen “3 IAN” = sama malli ja revisio. Ei “identtinen”: manuaalit eroavat satoja sivuja Nimi luettuna manuaalin kannesta. 6 781 nimeä korvattu katalogin lyhenteen tilalta Katalogin alkuperäinen nimi jää hoverille. Korjaus ei saa hävittää sitä mitä lähde sanoi Revisiolinja: katkoviiva = ei tällä markkinalla. Piilottaminen näyttäisi kattavuudelta — aukko on tieto Painike per kieli, vain jos useampi on näkyvissä. Sama tuote, eri manuaali
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.

LOGO FI EN TEEMA SARJA KÄYTTÖVOIMA MARKKINA PAKKAUS NÄYTÄ 20 V akku 1926 verkko 3205 kaasu 0 Hae nimestä, mallista tai tuotenumerosta… Uusin ensin 2 932 tuotetta seuraava erä täyttyy vieritettäessä
KUVA 3Työpöytä. Suodatinrivit ovat kaksisarakkeinen ruudukko: nimiö 96 px:n sarakkeessa, suodatinnapit viereisessä. Sarakeleveyden määrää pisin suomenkielinen nimiö, ei englanninkielinen.
390 px · SUODATTIMET KIINNI Suodattimet 3 Hae… 390 px · SUODATTIMET AUKI Suodattimet KÄYTTÖVOIMAMARKKINAPAKKAUS Nimiö siirtyy nappien yläpuolelle. .facet-chips tarvitsee min-width: 0 — muuten nowrap-napit leventävät sarakkeen yli 1fr:n ja sivu vierii sivusuunnassa Otsake muuttuu staattiseksi alle 760 px. Tarttuvana se peitti 750 px 813 px:n ruudusta Laskuri painikkeessa on pakollinen. Suodatettu näkymä ei saa näyttää samalta kuin suodattamaton, kun rivit ovat piilossa Testaus vaatii iframen. Headless Chrome kiinnittää asetteluikkunan 500 px:iin, joten --window-size=390 renderöi 500 px:n asettelun ja rajaa sen — näyttää täsmälleen ylivuotovialta
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.

ValittuKieli­kohtaiset 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 sanoiKortti sanooIAN
Bensiinik. ketjusaha KATBensiinikäyttöinen ketjusaha495510
Akkuk. ruohonleikkuri i akkua40 V Akkuruohonleikkuri507920
Akkuk. 3 in 1 -yleispuhallin12 V Akkukäyttöinen pieni yleispuhallin 3 in 1504168
Αθόρ.αεροσυμπ.Silent air compressor339048
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.

RiviKysymys jonka se esittääEsimerkkejä
MarkkinaOnko tätä myyty täällä?FI · EN · DE
KäyttövoimaMitä minun täytyy omistaa, jotta tämä toimii?20 V akku · verkkovirta · bensiini
PakkausMitä laatikossa on?akku · laturi · sarja · pelkkä työkalu
SarjaOnko tämä PERFORMANCE vai erikoiserä?PERFORMANCE · limited
RevisioMonesko versio tästä mallista?A · B · C · …
VuosiMiltä 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.

KÄYTTÖVOIMAPAKKAUSRYHMÄ 20 V akku TAI 12 V akku verkkovirta JA pelkkä työkalu akku laturi JA puutarha painepesu akku- tai verkkokäyttöiset puutarhatyökalut, joiden laatikossa ei ole akkua 3 tuotetta Useampi valinta samalla rivillä LAAJENTAA · valinta uudella rivillä KAVENTAA
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.SarjaRev.VuosiHaku
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.

RiviNappejaMistä ne tulevat
Markkina3DE · EN · FI
Käyttövoima1514 käyttövoimaa + “tarvitsee tarkistuksen”
Pakkaus10sisältö, ryhmäliput ja “ei mitään näistä”
Sarja2perus · PERFORMANCE
Revisio9A–I, mallilinjan pituus datassa
Vuosi172011–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.