Käyttöönotto
Paketti kulkee palveluun vendoroituna kopiona. Alla paketin README:n luvut sellaisinaan, sekä kaksi kohtaa, jotka ovat menneet väärin useammassa palvelussa.
Kaksi tunnettua ansaa
Fontti: --font-sans, ei --font-geist-sans
Teema lukee muuttujaa --font-sans. Vercelin geist/font/sans asettaa --font-geist-sans, jolloin fontti latautuu mutta mikään ei käytä sitä ja sivu näyttää järjestelmäfonttia. Lataa Geist next/font/localilla muuttujaan --font-sans. Mitattu TP-PERUSTUKSESSA 19.10. ja tällä sivustolla 22.10.2026.
const fontSans = localFont({
src: "../node_modules/geist/dist/fonts/geist-sans/Geist-Variable.woff2",
weight: "100 900",
variable: "--font-sans",
});Sävy: data-vari juureen
Sävyteemat ovat styles/savyt.css:ssä. Sävy valitaan juuren attribuutilla, teema .dark-luokalla. Ilman attribuuttia on paketin oletus, joka on sama kuin Neutraali.
@import "tailwindcss"; @import "@karkkainen/ui/styles/theme.css"; @import "@karkkainen/ui/styles/savyt.css"; <html data-vari="punainen">
Asennus
Paketilla on oma reponsa: [Karkkainen-AI/TP-DESIGN-SYSTEM](https://karkkainen.ghe.com/Karkkainen-AI/TP-DESIGN-SYSTEM) (luotu 2026-08-11). Paikallinen työpuu on /home/karkkai/tp-design-system.
Vendorointi on jakelutapa — ks. [AGENTS.md](AGENTS.md) §7 päätöksen perustelut. Huomaa että perustelu muuttui osittain kun oma repo saatiin: git-riippuvuus on nyt teknisesti mahdollinen, mutta AIMO:n .npmrc estää sen heillä (allow-git=none), joten vendorointi on yhä ainoa tapa joka toimii kaikille kolmelle.
Kopion tuottaminen
npm run vendor -- /polku/kuluttajaan/vendor/karkkainen-ui
Kohdepolku on vendor-hakemisto itse, ei kuluttajan repojuuri.
> Kontissa ajettaessa tarvitaan safe.directory, koska git kieltäytyy > lukemasta repoa jonka omistaja on eri kuin prosessin käyttäjä. Ilman sitä > ajo epäonnistuu kohdassa joka lukee commit-shan, ja virhe näyttää > likaisuustarkistukselta vaikka työpuussa ei ole vikaa: > > ``bash > docker run --rm -v /home/karkkai/tp-design-system:/ds -v /polku/kuluttajaan:/kohde \ > -w /ds node:22-alpine sh -c "apk add --no-cache git && \ > git config --global --add safe.directory /ds && \ > node scripts/vendor.mjs /kohde/vendor/karkkainen-ui" >
Työkalu kirjoittaa paketin kohdehakemistoon ja leimaa sen package.json:iin:
"vendored": {
"from": "tp-design-system",
"version": "0.1.0",
"commit": "2ab3db5e014b…",
"committedAt": "2026-08-11T06:04:38+02:00",
"contentHash": "67bce268e27d…"
}commit kertoo alkuperän, contentHash sisällön. Tarkistus nojaa jälkimmäiseen — muuten jokainen dokumentaatio- tai demo-commit merkitsisi kaikki kopiot vanhentuneiksi turhaan.
Työkalu kieltäytyy jos työpuu on likainen (muuten sha valehtelisi kopion sisällöstä), ja poistaa scripts-kentän kopiosta, koska se osoittaisi tiedostoihin joita ei vendoroida.
Kopion tarkistaminen
npm run vendor:check -- /polku/kuluttajaan/vendor/karkkainen-ui
Kertoo onko kopio ajan tasalla ja kuinka monta committia jäljessä se on. > ⚠️ Missä tämän voi ajaa: tarkistus lukee DS-lähderepon git-tilan ja > laskee sen sisällöstä tiivisteen, jotta se voi verrata. Se toimii siis vain > ympäristössä jossa sekä tämä repo että kuluttajan kopio ovat läsnä — > ylläpitäjän koneella tai cross-repo-CI:ssä. Sitä ei voi ajaa kuluttajan > käynnistysskriptissä eikä kontissa, jossa DS-repoa ei ole.
| Paluuarvo | Merkitys |
|---|---|
0 | ajan tasalla |
1 | vanhentunut — paketin sisältö on muuttunut lähteessä |
2 | ei leimaa — vendoroitu ennen tätä työkalua, alkuperää ei voi päätellä |
3 | kopiota on muokattu paikallisesti — sisältö ei vastaa sen omaa leimaa |
Paluuarvo 3 on syytä ottaa vakavasti: vendor/ on generoitua sisältöä ja käsin tehdyt muutokset katoavat seuraavassa päivityksessä. Vie ne DS-repoon.
> Tunnettu kompromissi: README.md ja CHANGELOG.md kopioidaan mukaan > mutta jätetään tiivisteestä pois (muuten jokainen changelog-merkintä > merkitsisi kaikki kopiot vanhentuneiksi). Seuraus: jos joku muokkaa > vendoroidun kopion README:tä tai CHANGELOG:ia, tarkistus ei huomaa sitä. > Muokkaustunnistus kattaa vain buildiin vaikuttavan sisällön. Se on > hyväksyttävää koska nuo tiedostot eivät vaikuta ajoon — mutta se on > kompromissi, ei ominaisuus.
Kuluttajan oma eheystarkistus
Kopion mukana matkustaa verify.mjs, joka toimii ilman DS-repoa — työpuussa, CI:ssä tai käynnistysskriptissä:
node vendor/karkkainen-ui/verify.mjs
Se laskee kopion oman sisällön tiivisteen ja vertaa sitä kopion omaan leimaan, eli havaitsee paikallisen muokkauksen. Ei riippuvuuksia, ei gitiä, ei verkkoa.
> ⚠️ Next.js `output: "standalone"` ei kopioi `verify.mjs`:ää imageen. > Standalone-tuloste sisältää vain tiedostot joita sovellus oikeasti > importtaa. verify.mjs ei ole importattu mistään, joten se karsiutuu — > kontissa on tällöin vain package.json. Todettu 2026-08-11: AIMO:n ja > TP-Vastaanoton konteissa tiedostoa ei ollut, TP-IAM:in oli (heidän > imagensa ei ole standalone). > > Versioleima toimii silti kaikkialla, koska package.json matkustaa > mukana — eli kysymykseen "mikä versio meillä on ajossa" saa vastauksen > kontista: > > ``bash > docker exec <kontti> cat /app/vendor/karkkainen-ui/package.json > > > Jos haluat muokkaustunnistuksen myös ajoympäristöön, kopioi tiedosto > eksplisiittisesti runtime-vaiheessa: > > dockerfile > COPY --from=build /app/vendor/karkkainen-ui/verify.mjs ./vendor/karkkainen-ui/verify.mjs > > > Useimmiten sitä ei tarvita ajossa: muokkaustunnistus kuuluu CI:hin ja > työpuuhun, jossa verify.mjs` on aina saatavilla.
| Paluuarvo | Merkitys |
|---|---|
0 | kopio vastaa leimaansa |
2 | leima puuttuu tai on tuntematonta tiivistemuotoa |
3 | kopiota on muokattu |
> Se ei kerro onko kopio jäljessä lähteestä — se vaatii pääsyn DS-repoon. > Staleness on ylläpitäjän vendor:check, muokkaus on kuluttajan > verify.mjs. Tarkistus ei myöskään ole turvamekanismi vaan vahingon > havaitsin: verify.mjs itse ei kuulu tiivisteeseen, joten tarkoituksellinen > peukalointi voi kiertää sen.
Kuluttajan pää
"dependencies": {
"@karkkainen/ui": "file:./vendor/karkkainen-ui"
}> ⚠️ Vendoroinnin tunnettu heikkous on että kopio irtoaa lähteestään > hiljaisesti. Leima ja vendor:check ovat olemassa juuri siksi — käytä > niitä, älä luota siihen että muistat.
Käyttöönotto palvelussa
1. Asenna peer-riippuvuudet
npm install react react-dom tailwindcss @tailwindcss/postcss tw-animate-css \ @radix-ui/react-dialog @radix-ui/react-dropdown-menu \ @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select \ @radix-ui/react-separator @radix-ui/react-slot @radix-ui/react-tabs \ @radix-ui/react-tooltip \ class-variance-authority clsx lucide-react next-themes tailwind-merge
Tailwind 4 ja `tailwind-merge` 3. Paketin luokat ovat v4:n nimiä (shadow-xs, outline-hidden), eikä tailwind-merge 2 tunne niitä — cn() jättäisi ristiriitaiset luokat yhdistämättä hiljaa.
2. PostCSS
// postcss.config.mjs
export default { plugins: { "@tailwindcss/postcss": {} } };Ei `tailwind.config.ts`:ää eikä `content`-listaa. Teema on CSS:ssä (kohta 3), ja se kertoo Tailwindille paketin lähdekoodin sijainnin itse (@source "../src"). v3:n yleisin käyttöönottovirhe — paketin polku puuttui contentista ja komponentit renderöityivät tyylittöminä, tai pahemmin: animate-pulse ja motion-reduce:animate-none karsiutuivat hiljaa — ei ole enää kuluttajan muistettava.
Varmista silti käännetystä CSS:stä eikä oletuksesta:
grep -o 'animate-pulse{[^}]*}' .next/static/css/*.css3. CSS
/* app/globals.css */
@import "tailwindcss";
@import "@karkkainen/ui/styles/theme.css"; /* tuo myös tokens.css:n */
@layer base {
body {
@apply bg-background text-foreground;
}
}theme.css sisältää värit, säteet, varjot, fontit, .dark-variantin, animaatiot (tw-animate-css), palvelutiilien väriluokat ja perustyylit: reunan oletusvärin (myös <table>) ja kirjasimen renderöinnin.
Tailwind 4:n omat oletukset ovat voimassa sellaisinaan, esimerkiksi painikkeiden cursor: default. Teema ei jäljittele v3:n ulkoasua.
> v3:sta siirtyvälle: poista tailwind.config.ts, autoprefixer ja > tailwindcss-animate, ja korvaa @tailwind-direktiivit yllä olevalla. > Oman koodisi luokat: npx @tailwindcss/upgrade hoitaa uudelleennimeämiset > (shadow-sm → shadow-xs, shadow → shadow-sm, outline-none → > outline-hidden, ring → ring-3). `rounded-sm`/`rounded-md`/`rounded-lg` > eivät muutu, koska teema määrittelee ne --radiusista kuten preset. > > Vanha * { @apply border-border } saa jäädä — arvo on sama.
Jos palvelusi käyttää React Flow'ta, tuo lisäksi valinnainen teemoitus:
@import "@karkkainen/ui/styles/theme.css"; @import "@karkkainen/ui/styles/react-flow.css";
Nämä säännöt asuivat 0.2.0:aan asti tokens.css:ssä, jolloin jokainen palvelu sai kolmannen osapuolen kirjaston teemoituksen käyttipä sitä tai ei. Tunnettu kuluttaja: TP-Vastaanoton /topologia.
4. Next.js: transpilePackages
Jos @karkkainen/ui shipataan TS-lähdekoodina (workspace-tilanne), Next.js pitää saada transpileeraamaan se:
// next.config.ts
const nextConfig = {
transpilePackages: ["@karkkainen/ui"],
};
export default nextConfig;Vite-pohjaisissa projekteissa transpilaatio toimii oletuksena.
5. Dockerfile: kopio on oltava paikallaan ennen npm ci:tä
`file:`-riippuvuus vaatii hakemiston olemassa asennushetkellä. Jos npm ci ajetaan ennen kuin vendor/ on kopioitu kuvaan, se kaatuu — ja virhe nimeää tämän paketin, eli näyttää paketin vialta vaikka on Dockerfilen järjestys.
WORKDIR /app COPY package.json package-lock.json ./ COPY vendor ./vendor # ← ennen npm ci:tä, ei sen jälkeen RUN npm ci
Ja `vendor/` on oltava buildin kontekstin sisällä. Kontekstin ulkopuolista polkua ei voi kopioida, joten kopio kuuluu sen package.jsonin viereen joka ilmoittaa riippuvuuden — monorepossa sovelluksen hakemistoon eikä repon juureen.
> Tämä puuttui README:stä 0.37.1:een asti, ja kuudes kuluttaja kaatui > siihen vaikka olin kertonut saman asian heille tuntia aiemmin — mutta > perusteluna kopion *sijainnille*, en käyttöönoton askeleena. Sama > vikaluokka kuin sortRowsin varoitus väärässä tiedostossa: oikea > teksti väärässä kategoriassa ei tavoita lukijaa ennen kuin päätös on jo > tehty.
6. Theme provider
// app/layout.tsx
import { ThemeProvider } from "@karkkainen/ui";
import "./globals.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fi" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
);
}> ⚠️ `defaultTheme`ia ei ole tässä esimerkissä, ja se on tarkoituksellista. > next-themesin oma oletus on enableSystemin kanssa "system", eli > käyttöjärjestelmän teema — ja se on parempi oletus kuin sen > ohittaminen. Rivin puuttuminen on itsedokumentoiva; defaultTheme="system" > näyttäisi valinnalta jonka joku voi myöhemmin "korjata" takaisin. > > Rivi `defaultTheme="light"` oli tässä esimerkissä 8.10.2026 asti. > TP-IAM löysi puutteen. Mitattu: rivi oli viidessä palvelussa, mutta > kolme niistä on vanhempi kuin tämä repo — se tuli TP-Vastaanoton > alkuperäisestä prototyypistä ja README peri sen extractissa. Esimerkki > ei siis ollut kaikkien lähde, mutta se olisi ollut jokaisen seuraavan. > > Jos vaihdat oletuksen jälkikäteen, mittaa ensin tummien tokenien > kattavuus. Palvelu jolla .dark on puutteellinen näyttäisi rikkinäisen > käyttöliittymän käyttäjille jotka eivät ole sitä pyytäneet, ja se on > pahempi kuin epäyhtenäinen oletus. Pelkällä tokens.css:llä kattavuus on > täysi: 28 tokenia :rootissa, 25 .darkissa, ja ne kolme eroa > (--brand, --brand-foreground, --radius) kuuluu pitää samoina. > Mittaa oma repo, koska omat lisätokenit näkyvät vain siellä.
7. Kehykset ja kuori (vaihe D)
Frame-perhe, TableFrame ja AppFrame (0.54.0–0.55.0, päätökset 0003–0005). TP-PERUSTUS siirtyi niihin 22.10.2026 ilman näkyvää muutosta (pikselivertailu 55 näkymää, 0 eroa). Alla ne kohdat, jotka siinä vaativat työtä. Lue ne ennen kuin aloitat.
Versio ja tyylit. Vähintään 0.55.0. theme.css tuo frame.css:n itse; savyt.css vain, jos palvelu käyttää paketin sävyteemoja (<html data-vari="…">).
Tokenit ovat HSL-kolmikoita. --frame, --frame-border, --sidebar, --nav-active ja --nav-active-foreground luetaan muodossa hsl(var(--x)). Jos palvelu on määritellyt jonkin niistä itse kokonaisena värinä (#fafafa, oklch(…)), bg-sidebar ja muut apuluokat ovat virheellisiä eikä selain kerro siitä. Määrittele omat sävysi kolmikkoina: --frame: 192 37.5% 97.4%.
Kerrokset: kaksi sääntöä eri kaskadissa.
- Kehys itse (
[data-frame]: kulmat, reunan väri, varjo) on@layer components-kerroksessa: palvelun apuluokka voittaa sen. Älä siis anna kehykselleshadow-xs-luokkaa tai omaa reunaväriä: ne korvaavat kehyksen varjon ja sisäreunan viivan. - Kehyksen taulukko (
[data-table-frame-body]: solujen täyte, tasaus, otsakkeen korkeus) on kerrosten ulkopuolella, koska paketin omaTableCell(p-3 align-middle) muuten voittaisi sen. Solun omapx-*-luokka ei siis vaikuta kehyksessä. Yksittäinen toimintosolu keskelle:<div data-cell-center>solun sisällä. Rivien tasaus:TableFrame align="top"tai"middle". - Solut eivät rivity (
white-space: nowrap), jotta lyhyet arvot pysyvät rivillään. Pitkä vapaa teksti (tuotenimi, kuvaus) merkitään<TableCell data-wrap>: solu rivittyy ja on vähintään 10rem leveä. Ilman merkintää pitkät nimet työntävät muut sarakkeet vaakavierityksen taakse. - Rivin tausta tulee kehykseltä. Kerroksettomat säännöt maalaavat solujen taustan ja nollaavat rivin hoverin, joten rivin oma
bg-*- taihover:bg-*-luokka ei näy kehyksessä, eikä siitä tule virhettä. Valittu rivi merkitäändata-state="selected"(<TableRow data-state="selected">), ja hover tulee kehykseltä. Jos palvelu korosti valitun rivin luokalla (className="bg-accent"), korostus katoaa siirrossa hiljaa (TP-TTK:n vertaisarvio).
Kaksi kehysmuotoa. Frame ilman muuta = sisennetyt paneelit (FramePanel, useita rinnakkain). Yksi paneeli reunasta reunaan: <Frame className="p-0"> + FrameBody. Mittari: StatFrame. Otsikon rivinkorkeus on leading-tight; jos palvelun oma otsikko oli tavallisella rivillä, anna otsikko <span className="leading-5">-kääreessä, muuten nauha lyhenee 2,5 px.
Kuori: `AppFrame`. Kehykset eivät vaadi kuoren vaihtoa: AppShellissä pysyvä palvelu voi ottaa ne käyttöön sellaisenaan, koska AppShell asettaa --shell-topin itse (3rem, insetissä 3.625rem; PR #7). TableFramen tahmea työkalurivi toimii siis molemmissa kuorissa (TP-MEDIAn vertaisarvio).
| Palvelussa ennen | AppFramessa |
|---|---|
| sivupalkin ylälaita (palvelun vaihtaja) | sidebarHeader |
| käyttäjätili sivupalkin alalaidassa | sidebarFooter; kääre lisää p-2, vähennä oma täyte saman verran |
| murupolku yläpalkissa | headerStart |
| haku, kapealla käyttäjätili | headerEnd |
| päivitys- tai tilanauha ylimpänä | banner |
| sivujen oma täyte | contentClassName="px-0 pb-0 md:px-0", jos sivut tekevät täytteen itse |
AppShellista siirtyvälle, propsi kerrallaan:
| AppShell | AppFrame |
|---|---|
product, services, nav, navFooter, currentPath, LinkComponent | samat |
productHref | linkki sidebarHeaderiin (esim. <Link href="/">palvelun nimi</Link>) |
user (esim. UserMenu + onLogout) | leveällä sidebarFooter, kapealla sama kompaktina headerEndissä, koska sivupalkki on kapealla piilossa |
search, actions | headerEnd |
contentClassName | contentClassName (oletus: keskitetty sisältöalue 1146 px, kääre 1194 px − 2 × 24 px täyte, päätös 0006; max-w-none toimii kuten ennen) |
sidebarCollapsible | aina pienennettävä |
sidebarStorageKey | sidebarStorageKey (sama avain säilyttää käyttäjän valinnan) |
variant="inset" | AppFrame on aina inset-muotoinen |
hideSidebar, defaultSidebarCollapsed | ei vastinetta; jos tarvitset, ilmoita PR:llä |
- Tahmeat elementit kiinnittyvät kohtaan
var(--shell-top), jonka AppFrame asettaa yläpalkin korkeudeksi. Korvaa omat vastaavat muuttujat sillä, ja myös Tailwind-literaalit, jotka olettavat yläpalkin korkeuden: esim.lg:top-16 lg:max-h-[calc(100vh-5rem)]→lg:top-[calc(var(--shell-top)+1rem)] lg:max-h-[calc(100dvh-var(--shell-top)-2rem)](TP-TTK:n vertaisarvio). - Sivupalkin tunniste on
tp-app-frame-sidebar. Omat CSS-valitsimet (#oma-sivupalkki nav …) on vaihdettava. - AppFramen esilatausskripti asettaa vain pienennetyn tilan (
data-sidebar). Jos palvelun oma esilataus asetti muutakin (esim. sävyn), siirrä se juurikuoreen. - AppFrame piirtää navigaation itse. Jos palvelu kuunteli navigaation klikkauksia kääreellä (optimistinen korostus), kuuntele niitä asiakirjatasolla sieppausvaiheessa.
- Vierityssäiliö on sisältökortti, ei ikkuna.
window.scrollTo(...)ei tee AppFramessa mitään. Vieritä kortti (lähin vierittävä esivanhempi) taielement.scrollIntoView(). TP-TTK:n mobiilinäkymä, joka vieritti valitun rivin tiedot näkyviin, lakkasi toimimasta hiljaa (TP-TTK:n siirto 22.10.2026). - Palvelun nimi pienennettynä:
sidebarHeaderissa nimi piiloon luokallalg:group-data-[sidebar=collapsed]/shell:hidden, jolloin pienennetyssä palkissa näkyy vain merkki.
Todennus. Pikselivertailu nykyistä tuotantoa vasten koeinstansseilla: kaikki sivut, molemmat teemat, kapea näyttö ja pienennetty sivupalkki. Ennen kuin luotat nollaeroon, varmista että sivuilla on dataa: kantavirhe tai tyhjä näkymä antaa myös nollan. TP-PERUSTUKSESSA kaksi vertailua näytti nollaa tyhjällä kannalla, ja vain porttiehto (kantavirhe sivulla kaataa ajon) paljasti sen. Työkalu vertailuun tulee tähän repoon (scripts/kuvavertailu).
- Datan portti. Staging lukee tuotannon dataa vain luku -oikeudella (dev/staging-malli 22.10.2026), joten se on luonteva todellisen datan vertailukohde. Kiinteä otos (esim. valmiit, tuloksen sisältävät tehtävät) pitää vertailun toistettavana. Kantavirheportti tarvitaan silti: tyhjä näkymä syntyy myös, kun lähde on alhaalla.
- CSS:n portti. Kerroksettomat säännöt ovat se kohta, jossa Lightning CSS voi pudottaa säännön varoituksella ja
next buildpäättyy silti koodiin 0. Lisää buildiin porttiehto, joka kaatuu riviin "warning while optimizing generated CSS" (TP-TTK:llä Dockerfilessä 21.10.2026 alkaen, todennettu mutaatiolla).
8. Uusi ulkoasu askel askeleelta
Kaikki palvelut tekevät uuden ulkoasun samassa järjestyksessä. Malli on TP-PERUSTUS (22.10.2026), ja jokaisella osalla on esimerkki koodeineen TP-DESIGN-SYSTEM-sivustolla (Kuviot ja Komponentit). Tee askel kerrallaan ja todenna jokainen kuvavertailulla (scripts/kuvavertailu) ennen seuraavaa: silloin tiedät, mikä muutos aiheutti eron.
Järjestys on suositus, ei riippuvuus: askel 3 (kehykset) toimii myös AppShellissä, koska se asettaa --shell-topin (kohta 7). Palvelu, joka on jo aloittanut kehyksistä, jatkaa siitä (TP-TTK teki askeleen 3 ennen askelta 1).
Tila: ✅ paketissa (kuoren osat 0.56.0)
Askel 0. Pohja
- ✅ Paketti vähintään 0.55.1 ja Tailwind 4. Tailwind 3 -palvelut tekevät ensin siirron (sivuston Siirtyminen-sivu ja kohdat 2–3 yllä).
- ✅ Fontti Geist
next/font/localilla muuttujaan--font-sansja Geist Mono muuttujaan--font-mono(paketin oletus; koodi-, EAN- ja tunnussarakkeet samalla fontilla kaikissa palveluissa). Lukitsegeist-paketin tarkka versio ("geist": "1.8.1", ei^1.8.1), jotta päivää vanha julkaisu ei tule mukaan huomaamatta (TP-TTK 22.10.2026:^ratkesi 1.8.2:ksi). Eigeist/font/sans(se asettaa muuttujan--font-geist-sans, jota teema ei lue). Fontin vaihto on omistajan hyväksyttävä palvelukohtaisesti. - ✅ Tumma teema:
ThemeProvider,.dark(kohta 6).
Askel 1. Kuori: sivupalkki, yläpalkki ja sisältökortti (`AppFrame`)
Tee ylhäältä alas:
- ✅ Sivupalkin ylälaita: palvelun vaihtaja
ServiceSwitchersidebarHeaderiin. Palvelun nimi on painike, ja sen valikossa ovat muut TP-palvelut monogrammeineen, nykyinen merkittynä. Pienennettynä näkyy monogrammi. Samaservices-lista kuinAppFramenservicesilla (kapealla yläpalkinAppLauncher). - ✅ Navigaatio:
nav(NavGroup[]: otsikko ja kohdat kuvakkeineen, laskuribadge) janavFooteralalaitaan (esim. Dokumentaatio, Versiohistoria). Ryhmän otsikko ja kohtien värit tulevat paketista. - ✅ Sivupalkin alalaita: oma tili -painike
AccountMenusidebarFooteriin, ja samacompact-muodossaheaderEndiin kapealle näytölle. Avatar, nimi ja sähköposti. Valikossa tilin linkit (links), teema (vaalea, tumma, järjestelmä), sävy (shades, jos palvelu tarjoaa) ja uloskirjautuminen (onLogout). Oma tili on tämä TP-PERUSTUKSEN mallin mukainenAccountMenu, ei vanhaUserMenu: koko rivi (avatar, nimi, sähköposti, ⇅) on painike, ja teemassa on myös Järjestelmä (omistaja 22.10.2026; TP-TTK:n UserMenu aukesi vain avatarista). - ✅ Yläpalkki: pienennyspainike (AppFrame), murupolku
headerStartiin, haku ja kapealla kompakti oma tiliheaderEndiin. Haku onHeaderSearch(SearchComboboxyläpalkin ulkoasulla, Ctrl+K / ⌘K, kapealla pyöreä hakupainike ja paneeli). Se vie kohteeseen; listan rajaus on erikseenTableFramen työkalurivillä. - ✅ Nauha ylimpänä:
banner(esim. päivitysilmoitus). - ✅ Ympäristömerkki:
AppFrame environment={process.env.YMPARISTO}→ "Koeversio" (staging) tai "Kehitysversio" (dev) murupolun perään. Tuotannossa arvo puuttuu, eikä merkkiä näy. Kuoreen eikä sivuille, koska stagingissa data on samaa kuin tuotannossa eikä niitä muuten erota. - ✅ Vieritys ja tahmeat:
--shell-top, vierityssäiliönä kortti (kohta 7).
Askeleen 1 todennus kirjautuneena. Ilman käyttäjää oma tili -painike ja kompakti tili jäävät tyhjiksi, eikä kuva paljasta sitä (TP-TTK:n vertaisarvio). Aja vertailu kirjautuneena: devissä jaetulla testitilillä, tai koeinstanssissa tekaistulla käyttäjällä (TP-PERUSTUKSEN avoimessa tilassa "Operaattori"). Lisää asetuksiin vaadittu, joka tarkistaa käyttäjän nimen sivupalkista.
Askel 2. Sivun alku
- ✅
PageHeader: yläotsikko (eyebrow), otsikko, kuvaus ja toiminnot. - ✅ Mittarit rivinä:
StatFrame(kuvake, selite, luku, lukuohje).
Askel 3. Sisältö kehyksiin
- ✅ Otsikolliset lohkot:
FramejaFramePanel(sisennetyt paneelit) taiFrame className="p-0"jaFrameBody(yksi paneeli reunasta reunaan). - ✅ Taulukot:
TableFrame. Työkaluriville haku, suodattimet ja näkymän kytkinSegmented variant="surface", alatunnisteeseenTableFramePagination. SarakkeetDataTablella, valittu rividata-state="selected"jaaria-selected="true"(data-state ohjaa vain ulkoasua, ruudunlukija lukee aria-selectedin), toimintosoludata-cell-center. - ✅ Tyhjät tilat:
EmptyState.
Askel 4. Tilat ja vuorovaikutus
- ✅ Tila taulukon riveillä
StatusDot, yksittäinen tilaStatusBadge. Aina sanoin, ei pelkkänä värinä. - ✅ Valintaikkunat
DialogjaFormDialog. Rivin tiedot vierelleSidePanel, päälleSheet. IlmoituksetToast.
Askel 5. Viimeistely
- ✅ Sävyteemat (0.58.0, kaikille palveluille):
savyt.css,ThemeProvider shadesjaAccountMenu shades. Valinta kulkee jaetussatp-savy-evästeessä, joten käyttäjän sävy on sama kaikissa palveluissa (ks. AccountMenu alla). - ✅ Versioilmoitus
VersionNotice product="…"kuoren rinnalle. Palvelu tarjoaa reitin/api/versio, joka palauttaa{ sha, builtAt }kuvan rakennuksesta. - ✅ Tarkista tumma teema ja kapea näyttö jokaisella sivulla.
Julkaisutahti: askeleet 0–1 yhtenä julkaisuna, 2–3 toisena ja 4–5 kolmantena. Jokaisesta kuvavertailu ja ilmoitus TP-DEVOPSille kuten muistakin vaihdoista. Jos jotain puuttuu paketista, tee se PR:llä pakettiin äläkä palvelun omaksi kopioksi. Juuri omista kopioista vaihe B joutui luopumaan.