@klodd/ds 3.20.1 → 3.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -98,3 +98,36 @@
98
98
  background: var(--surface-default);
99
99
  border-color: var(--border-subtle);
100
100
  }
101
+
102
+ /* --na: AI har inte bedömt (relevance_score IS NULL). Klickbar för att
103
+ trigga rescore. Dashed border signalerar "bedömning saknas, klicka för
104
+ att försöka". Lyft från Jubb-domain v3.21.1 (2026-05-13).
105
+ Användning: <button class="score-pill score-pill--na" data-action="rescore" data-job-id="X">N/A</button> */
106
+ .score-pill--na {
107
+ color: var(--text-muted);
108
+ background: var(--surface-default);
109
+ border: 1px dashed var(--border-default);
110
+ cursor: pointer;
111
+ font-family: inherit;
112
+ transition: background var(--dur-fast) var(--ease-default),
113
+ color var(--dur-fast) var(--ease-default);
114
+ }
115
+ @media (hover: hover) and (pointer: fine) {
116
+ .score-pill--na:hover {
117
+ background: var(--surface-hover);
118
+ color: var(--text-default);
119
+ }
120
+ }
121
+ .score-pill--na:active { transform: scale(0.96); }
122
+ .score-pill--na:focus-visible {
123
+ outline: 2px solid var(--border-focus);
124
+ outline-offset: 2px;
125
+ }
126
+ .score-pill--na:disabled,
127
+ .score-pill--na[aria-disabled="true"] {
128
+ opacity: 0.5;
129
+ cursor: progress;
130
+ }
131
+ .score-pill--na.is-loading {
132
+ background: var(--surface-active);
133
+ }
@@ -122,6 +122,18 @@
122
122
  }
123
123
  }
124
124
 
125
+ /* --logout: subtil ghost-variant for "Logga ut"-knapp. Opacity 0.7
126
+ gor knappen visuellt mindre framtradande an primar-actions men
127
+ fortfarande klickbar. Lyft fran Ekonoms .logout-btn (v3.21.0).
128
+ Anvands som modifier ovanpa .btn--ghost --sm:
129
+ <button class="btn btn--ghost btn--sm btn--logout" data-action="logout">Logga ut</button> */
130
+ .btn--logout {
131
+ opacity: 0.7;
132
+ }
133
+ @media (hover: hover) and (pointer: fine) {
134
+ .btn--logout:hover { opacity: 1; }
135
+ }
136
+
125
137
  /* --positive: transparent + positive-border. Bekraftande action
126
138
  (t.ex. "Gemensam"-flagga i review_queue). Lyft fran Ekonom v3.9.0. */
127
139
  .btn--positive {
@@ -0,0 +1,57 @@
1
+ /* ================================================================
2
+ components/profile-panel.css
3
+ Centrerad profilkort: avatar (lg) + namn + email.
4
+ Anvands pa /jag (Ekonom) och /min-sida (Jubb).
5
+
6
+ Blocks:
7
+ .profile-panel - centrerad flex-column med komposit-children
8
+
9
+ Inga BEM-element - children ar paketets atomer (.avatar, .heading,
10
+ .inline-edit, .text-caption, .text-meta). Descendant-overrides
11
+ kalibrerar margin/layout for child-positions inom kortet.
12
+
13
+ Anvandning:
14
+ <div class="panel profile-panel">
15
+ <span class="avatar avatar--lg">CE</span>
16
+ <p class="text-meta">calle@klodd.io</p>
17
+ </div>
18
+
19
+ Med inline-edit (Ekonoms /jag-mönster):
20
+ <div class="panel profile-panel" data-profile-panel>
21
+ <span class="avatar avatar--lg">CE</span>
22
+ <p class="heading inline-edit">
23
+ <span class="inline-edit__text">Calle</span>
24
+ <button class="inline-edit__btn">...</button>
25
+ </p>
26
+ <p class="text-caption">calle@klodd.io</p>
27
+ </div>
28
+
29
+ Lyft fran Ekonoms .profile-panel-centered + Jubbs .profile-panel
30
+ (v3.21.0, 2026-05-13). Single source of truth ersatter app-domain-
31
+ kopior i ekonom.css + jubb.css.
32
+ ================================================================ */
33
+ .profile-panel {
34
+ display: flex;
35
+ flex-direction: column;
36
+ align-items: center;
37
+ text-align: center;
38
+ gap: var(--space-8);
39
+ padding: var(--space-20) var(--space-16);
40
+ }
41
+
42
+ .profile-panel .avatar--lg {
43
+ margin-bottom: var(--space-4);
44
+ }
45
+
46
+ .profile-panel .inline-edit {
47
+ display: inline-flex;
48
+ align-items: center;
49
+ gap: var(--space-6);
50
+ justify-content: center;
51
+ margin: 0;
52
+ }
53
+
54
+ .profile-panel .text-caption,
55
+ .profile-panel .text-meta {
56
+ margin: 0;
57
+ }
package/css/index.css CHANGED
@@ -41,6 +41,7 @@
41
41
  /* v2.0.0 - flyttade fran app-repona */
42
42
  @import './components/banner.css';
43
43
  @import './components/panel.css';
44
+ @import './components/profile-panel.css';
44
45
  @import './components/hub-card.css';
45
46
  @import './components/stat.css';
46
47
  @import './components/form.css';
package/css/utilities.css CHANGED
@@ -113,3 +113,68 @@
113
113
  /* Text-color utilities (CSP-migration: ersatter style="color: var(--text-X)") */
114
114
  .text-subtle { color: var(--text-subtle); }
115
115
  .text-muted { color: var(--text-muted); }
116
+
117
+
118
+ /* ================================================================
119
+ Layout-utilities (v3.21.0 lyft fran Jubb-domain)
120
+ ================================================================ */
121
+
122
+ /* Logout-rad: centrerad btn-wrapper. Anvands pa /jag (Ekonom) och
123
+ /min-sida (Jubb) som container for "Logga ut"-knappen. */
124
+ .logout-row {
125
+ display: flex;
126
+ justify-content: center;
127
+ margin-top: var(--space-24);
128
+ margin-bottom: var(--space-16);
129
+ }
130
+
131
+
132
+ /* ================================================================
133
+ Text-utilities (v3.21.0 lyft fran Jubb-domain)
134
+ ================================================================ */
135
+
136
+ /* Inline-separator "·" mellan meta-text-items. Disabled-fargad
137
+ sa den fungerar som tyst avgransning utan att dra blicken. */
138
+ .dot-sep {
139
+ margin: 0 var(--space-6);
140
+ color: var(--text-disabled);
141
+ }
142
+
143
+ /* Pre-formatted code/text. Sunken bakgrund, monospace, pre-wrap
144
+ for line-break-preservering med word-wrap pa lang text. */
145
+ .code-block {
146
+ margin: 0;
147
+ padding: var(--space-12);
148
+ background: var(--surface-sunken);
149
+ border-radius: var(--radius-14);
150
+ font-family: var(--font-mono);
151
+ font-size: var(--fs-12);
152
+ color: var(--text-subtle);
153
+ white-space: pre-wrap;
154
+ overflow-wrap: break-word;
155
+ }
156
+
157
+ /* Pre-formatted long-text (job-descriptions, multi-line content).
158
+ Likt .code-block men sans-font + default-fargad text. */
159
+ .description-block {
160
+ margin: 0;
161
+ font-family: var(--font-sans);
162
+ font-size: var(--fs-13);
163
+ color: var(--text-default);
164
+ white-space: pre-wrap;
165
+ overflow-wrap: break-word;
166
+ line-height: var(--lh-base);
167
+ }
168
+
169
+ /* ul med default discs + standardspacing. Plain bullet-lista. */
170
+ .bullet-list {
171
+ margin: 0;
172
+ padding-left: var(--space-20);
173
+ }
174
+
175
+ .bullet-list > li {
176
+ margin-bottom: var(--space-4);
177
+ font-size: var(--fs-13);
178
+ color: var(--text-default);
179
+ line-height: var(--lh-base);
180
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@klodd/ds",
3
- "version": "3.20.1",
3
+ "version": "3.21.1",
4
4
  "description": "Klodd shared design system - tokens, components, JS",
5
5
  "main": "css/index.css",
6
6
  "bin": {
@@ -34,10 +34,11 @@ För varje entry gäller:
34
34
 
35
35
  ### badge (`badge.css`)
36
36
  - **Blocks:** `.badge`, `.score-pill` (sibling, kanonisk definition)
37
- - **Modifiers:** `.badge--neutral/-success/-warning/-danger/-accent`, `.score-pill--strong/-medium/-low`
38
- - **Anvand:** Status-pills (statiska)
37
+ - **Modifiers:** `.badge--neutral/-success/-warning/-danger/-accent`, `.score-pill--strong/-medium/-low`, `.score-pill--na` (v3.21.1 - klickbar N/A för rescore-trigger)
38
+ - **States på `.score-pill--na`:** `.is-loading`, `[aria-disabled="true"]`, `:disabled` (cursor: progress)
39
+ - **Anvand:** Status-pills (statiska) + score-pill--na som klickbar rescore-trigger (vanligtvis som `<button>`-element med `data-action="rescore"`)
39
40
  - **INTE:** Klickbara chips (anvand `.chip`)
40
- - **Tokens:** `--positive/-dim/-border`, `--warning/-dim/-border`, `--bg-danger`, `--accent-text/-a2/-a6`
41
+ - **Tokens:** `--positive/-dim/-border`, `--warning/-dim/-border`, `--bg-danger`, `--accent-text/-a2/-a6`, `--surface-default/-hover/-active`, `--border-default/-focus/-subtle`
41
42
 
42
43
  ### card (`card.css`)
43
44
  - **Blocks:** `.card`
@@ -133,6 +134,15 @@ För varje entry gäller:
133
134
  - **Modifiers:** `.panel--info`, `.panel--info-warning`, `.panel--danger`, `.panel--attention`
134
135
  - **Anvand:** Sektion med ramad styling och titel
135
136
  - **INTE:** Klickbart kort (anvand `.card--interactive`)
137
+ - **HTML-tag for `.panel__title`:** `<h3>` default. Specialfall: `<span>` inom `.panel__step-row` (compound badge+title), `<summary>` inom `<details>`-element. Se ADR 0015 (panel-title-tag-doctrine).
138
+
139
+ ### profile-panel (`profile-panel.css`) - v3.21.0
140
+ - **Blocks:** `.profile-panel`
141
+ - **Anvand:** Centrerad profilkort med avatar + namn + email/meta. Pa /jag (Ekonom) och /min-sida (Jubb).
142
+ - **INTE:** Allmant kort med titel (anvand `.panel` med `.panel__title`)
143
+ - **Tokens:** `--space-8/-16/-20`, `--space-4` (avatar margin), `--space-6` (inline-edit gap)
144
+ - **Composition:** Inga egna BEM-element. Children ar paketets atomer (`.avatar--lg`, `.heading`, `.inline-edit`, `.text-caption`, `.text-meta`). Descendant-overrides for child-positions inom kortet.
145
+ - **Lyft fran:** Ekonoms `.profile-panel-centered` + Jubbs `.profile-panel` (v3.21.0).
136
146
 
137
147
  ### hub-card (`hub-card.css`)
138
148
  - **Blocks:** `.hub-card`, `.hub-list` (sibling-wrapper for kort-lista), `.hub-category` (sibling-rubrik over hub-list)
@@ -0,0 +1,147 @@
1
+ # 0014 - CSS Specificity Doctrine
2
+
3
+ ## Status
4
+ Locked.
5
+
6
+ ## Context
7
+
8
+ Sprint 2026-05-13 avslöjade en tyst CSS-specificity-bugg i kv-list.css.
9
+ Default-regeln var skriven som:
10
+
11
+ ```css
12
+ .kv-list > li:not(.kv-list__item--budget):not(.kv-list__item--divider) {
13
+ color: var(--text-subtle);
14
+ font-size: var(--fs-13);
15
+ }
16
+ ```
17
+
18
+ Specificity blev 0,3,1 (3 klasser via .kv-list + 2x :not()-arg, plus 1
19
+ li element). Modifier-reglerna `.kv-list__item--strong` (color: text-
20
+ default + medium weight) och `.kv-list__item--sub` (font-size: fs-11)
21
+ hade specificity 0,1,0 - kunde inte vinna mot 0,3,1.
22
+
23
+ Symptom: Calle/Lisa/Totalt-rader förblev text-subtle istället för
24
+ text-default (font-weight: medium tillämpades dock - delvis vinst gav
25
+ intryck att modifier "fungerade"). Verklig kostnad förblev fs-13
26
+ istället för fs-11.
27
+
28
+ Tyst bugg eftersom font-weight på --strong och font-size på --sub
29
+ INTE konflikterade med default-regeln, så modifier-classes såg ut att
30
+ fungera. Visuell skillnad förlorades men ingen runtime-error.
31
+
32
+ Felmönstret: `:not()`-filter på default-regeln drog upp specificity
33
+ till en nivå där flat modifier-classes inte kunde override:a.
34
+
35
+ ## Decision
36
+
37
+ **Locked regel:** default/base-regler för rad-layout, list-items,
38
+ comp-bas-styling **får inte** sätta `color`, `font-size`, `font-weight`,
39
+ `background`, `border`, `text-transform`, `letter-spacing` när
40
+ modifier-classes existerar för samma property.
41
+
42
+ Tre alternativ för att undvika konflikt:
43
+
44
+ ### Alternativ 1: Inheritance från block-parent (rekommenderat)
45
+
46
+ Sätt default-properties på **block-roten** (`.X`), inte på item-rule
47
+ (`.X__item` eller `.X > li`).
48
+
49
+ ```css
50
+ /* RÄTT */
51
+ .kv-list {
52
+ color: var(--text-subtle);
53
+ font-size: var(--fs-13);
54
+ }
55
+
56
+ .kv-list > li:not(--budget):not(--divider) {
57
+ display: flex;
58
+ padding: var(--space-8) 0;
59
+ /* INGEN color/font-size */
60
+ }
61
+
62
+ .kv-list__item--strong { /* specificity 0,1,0 - vinner via cascade */
63
+ color: var(--text-default);
64
+ font-weight: var(--fw-medium);
65
+ }
66
+ ```
67
+
68
+ Alla item-rader **ärver** color/font-size från `.kv-list`. Modifier-
69
+ classes (0,1,0) override:ar via inheritance utan specificity-konflikt.
70
+
71
+ ### Alternativ 2: Höj modifier-specificity
72
+
73
+ Om Alternativ 1 inte funkar (t.ex. parent har inte naturlig
74
+ inheritance-relation), matcha modifier-specificity till default-rule:
75
+
76
+ ```css
77
+ .X > li.X--strong:not(.X--budget):not(.X--divider) { color: ...; }
78
+ ```
79
+
80
+ Brus och svårläsligt - undvik om möjligt.
81
+
82
+ ### Alternativ 3: :where() runt default-rule
83
+
84
+ ```css
85
+ :where(.X > li:not(.X--budget):not(.X--divider)) {
86
+ color: var(--text-subtle); /* specificity 0,0,0 */
87
+ }
88
+ ```
89
+
90
+ `:where()` nollställer specificity. Modifiers vinner trivialt. Modernt
91
+ CSS (alla browsers sedan 2021). Använd när inheritance inte fungerar
92
+ och Alternativ 2 är för fult.
93
+
94
+ ## Konsekvenser
95
+
96
+ **Bra:**
97
+ - Modifier-classes vinner förutsägbart utan specificity-hack
98
+ - Default-rules kan ha `:not()`-filter för layout-undantag utan att
99
+ förstöra modifier-override
100
+ - Inheritance-based design ger renare CSS (mindre upprepning)
101
+
102
+ **Trade-offs:**
103
+ - Kräver att block-parent (.X) finns och har naturlig
104
+ inheritance-relation
105
+ - Inheritance fungerar inte för layout-properties (display, padding,
106
+ gap) - dessa måste fortfarande sättas på item-rule
107
+ - Lite mer tankearbete vid komponent-design (vad är "default" och vad
108
+ är "modifier-overridable")
109
+
110
+ ## Migration: kv-list 3.19.0 → 3.19.3
111
+
112
+ 3.19.1 introducerade `:not()`-filter på default-rule (för --budget +
113
+ --divider exclusion) som gav specificity 0,3,1. Modifier-reglerna
114
+ tappade tyst. Tre patches behövdes:
115
+
116
+ - 3.19.1: layout-fix för bare LI (introducerade buggen)
117
+ - 3.19.2: fix layout för --budget (lade till :not()-filter)
118
+ - 3.19.3: flyttade color/font-size från item-rule till .kv-list-
119
+ parent (löste specificity-konflikten)
120
+
121
+ ## Audit över paketet
122
+
123
+ Sprint 2026-05-13 audit över alla 39 paket-CSS-filer hittade INGEN
124
+ liknande bugg utöver kv-list. Övriga komponenter har:
125
+ - Default-rules på 0,1,0 (.X { ... }) - matchas av modifier 0,1,0 via
126
+ cascade-order
127
+ - Context-overrides på 0,2,0 (.X--mod .X__element) - intentional för
128
+ cross-element-styling, inte konflikt mot self-modifier
129
+
130
+ Mönstret som triggade kv-list-buggen var unikt: `:not()`-filter på
131
+ en default-rule som också konkurrerade om color/font-size med
132
+ modifiers på samma block. Det mönstret ska undvikas framöver.
133
+
134
+ ## Future work
135
+
136
+ 1. **Stylelint-plugin** för att varna när default-rules sätter samma
137
+ property som modifier-rules i samma fil.
138
+ 2. **Custom selector-specificity-linting**: flagga rules med
139
+ specificity > 0,2,0 som inte är context-overrides.
140
+
141
+ ## References
142
+
143
+ - klodd-ds commit 9854004 (3.19.3 fix)
144
+ - kv-list.css JSDoc-header rad 24-35
145
+ - DESIGN-LANGUAGE.md sektion 14 (CSS-specificity-disciplin)
146
+ - ADR 0011-strikt-bem-elementsyntax.md (besläktad)
147
+ - ADR 0013-visual-coherence-doctrine.md (besläktad)
@@ -0,0 +1,122 @@
1
+ # 0015 - Panel-title-tag-doctrine
2
+
3
+ ## Status
4
+ Locked.
5
+
6
+ ## Context
7
+
8
+ Audit 2026-05-13 kvällsessionen avslöjade två problem med
9
+ `.panel__title`-användning över Klodd-ekosystemet:
10
+
11
+ **Problem 1: Inkonsekvent HTML-tagg i Ekonom.**
12
+ - 35 platser använder `<div class="panel__title">` (94%)
13
+ - 2 platser använder `<span class="panel__title">` (auto_kategorisering.html)
14
+ - 1 plats använder `<summary class="panel__title">` (bolan.html)
15
+
16
+ `<div>` saknar heading-semantik. Screen readers kan inte navigera
17
+ mellan paneler via heading-nav. Sökmotorer ser ingen rubrik-hierarki.
18
+
19
+ **Problem 2: Jubb använder `.hero__label` som panel-rubrik.**
20
+ - 25 platser har `<p class="hero__label">` som rubrik för en panel som
21
+ innehåller list/text/textarea - INTE följd av `.hero__amount`.
22
+ - Bara 1 plats är legitim hero-kontext (dashboard.html med pending_count
23
+ som hero-amount).
24
+
25
+ Felanvändningen uppstod eftersom `.hero__label` och `.panel__title`
26
+ visuellt liknar varandra (12px vs 11px, samma color/weight/letter-
27
+ spacing). Felet kostade inget visuellt, men semantiskt är klassen
28
+ "label OVAN en hero-amount" - inte en generisk paneltitel.
29
+
30
+ ## Decision
31
+
32
+ **Locked regel:** panel-rubriker använder `<h3 class="panel__title">`
33
+ som default-tagg.
34
+
35
+ ### Specialfall (två dokumenterade undantag)
36
+
37
+ **1. Inline i `.panel__step-row` eller `.panel__title-row`:**
38
+
39
+ Använd `<span class="panel__title">` när rubriken sitter inline i en
40
+ flex-rad med andra element (step-badge, meta, link). Block-element
41
+ (h3) bryter flex-layouten.
42
+
43
+ ```html
44
+ <div class="panel__step-row">
45
+ <span class="panel__step-badge">Steg 1</span>
46
+ <span class="panel__title">Bankavi-matchning</span>
47
+ </div>
48
+ ```
49
+
50
+ **2. Inom `<details>`-element:**
51
+
52
+ Använd `<summary class="panel__title">` eftersom native HTML kräver
53
+ `<summary>` som första child av `<details>`. Summary fungerar
54
+ analogt med en heading - aria-tree exponerar den som klickbar
55
+ expander.
56
+
57
+ ```html
58
+ <details class="panel">
59
+ <summary class="panel__title">Värderingshistorik (3)</summary>
60
+ <ul class="list mt-12">...</ul>
61
+ </details>
62
+ ```
63
+
64
+ ### `.hero__label` vs `.panel__title` - definition
65
+
66
+ - `<p class="hero__label">` används ENDAST om följt av `<p class="hero-amount">`
67
+ eller `<p class="hero__amount--card">`. Klassen är "labeltext OVANFÖR
68
+ en stor siffer-display".
69
+ - Allt annat (rubrik för list, table, form, text-content) ska vara
70
+ `<h3 class="panel__title">`.
71
+
72
+ Card-hero-pattern (panel som innehåller hero-mönster) är legitimt:
73
+
74
+ ```html
75
+ <div class="panel collapsible">
76
+ <p class="hero__label">Gemensamma utgifter</p>
77
+ <p class="hero__amount--card">12 500 kr</p>
78
+ </div>
79
+ ```
80
+
81
+ Här är `.hero__label` korrekt eftersom `.hero__amount--card` följer.
82
+
83
+ ## Konsekvenser
84
+
85
+ **Bra:**
86
+ - A11y-semantik korrekt: screen readers exponerar paneler som
87
+ navigerbara rubriker
88
+ - Industri-praxis (Radix UI, Material UI, Chakra renderar card-title
89
+ som h3 default)
90
+ - CSS oförändrad - `.panel__title`-stylling targetar klassen, inte
91
+ taggen
92
+ - Konsekvens över Klodd-ekosystemet (Jubb + Ekonom + framtida appar)
93
+
94
+ **Trade-offs:**
95
+ - Heading-hierarki är inte alltid h1→h2→h3 över hela sajten. Vyer utan
96
+ `<h1 class="heading-1">` får h3 som top-level (icke-ideal men inte
97
+ brott - browser-tree är OK med "skipping levels").
98
+ - Future custom stylelint-plugin kan enforce: `<h3 class="panel__title">`
99
+ default + dokumenterade specialfall. Tills dess är det human-disciplin.
100
+
101
+ ## Migration 2026-05-13
102
+
103
+ **Klodd-ds:** denna ADR + 02-components.md panel-entry uppdaterad +
104
+ DESIGN-LANGUAGE.md ny sektion 16 (HTML-tagg-konvention).
105
+
106
+ **Ekonom:** 35 `<div class="panel__title">` → `<h3 class="panel__title">`.
107
+ Specialfall (2 span + 1 summary) behålls med kommentar i template.
108
+
109
+ **Jubb:** 25 `<p class="hero__label">` (icke-hero-kontext) →
110
+ `<h3 class="panel__title">`. Legitim hero-användning (dashboard.html
111
+ rad 16) oförändrad.
112
+
113
+ Inga CSS-ändringar, ingen npm publish behövs. Bara templates +
114
+ dokumentation.
115
+
116
+ ## References
117
+
118
+ - DESIGN-LANGUAGE.md sektion 3 (hero vs page-header patterns)
119
+ - 02-components.md panel-entry
120
+ - 02-components.md hero-entry
121
+ - ADR 0011-strikt-bem-elementsyntax.md (besläktad - BEM-disciplin)
122
+ - ADR 0013-visual-coherence-doctrine.md (besläktad - visuell rytm)
@@ -0,0 +1,147 @@
1
+ # 0016 - "Mina sidor"-doctrine
2
+
3
+ ## Status
4
+ Locked.
5
+
6
+ ## Context
7
+
8
+ Audit 2026-05-13 jämförde Ekonoms `/jag` (sprint 66b-doktrinen) mot
9
+ Jubbs `/min-sida` (just byggd från grund). Båda är settings-hubbar -
10
+ samma roll men olika UI-disciplin.
11
+
12
+ Ekonom hade etablerat ett moget mönster (profilkort med inline-edit,
13
+ logisk sektion-gruppering, "Att åtgärda"-conditional, field_sheet-
14
+ macros för editing, LEAF-topbar). Jubb hade snabbt byggts utan motsvarande
15
+ struktur (profilkort utan namn, blandning av setting-rows och inline-knappar,
16
+ ingen actionable-conditional).
17
+
18
+ Per Calle's princip "alla appar ska ha samma namn och komponentstruktur"
19
+ behöver ett gemensamt locked pattern etableras för "Mina sidor" som
20
+ används av alla nuvarande och framtida Klodd-appar.
21
+
22
+ ## Decision
23
+
24
+ **Locked template-struktur för "Mina sidor"** (kanonisk ordning):
25
+
26
+ ```
27
+ ┌────────────────────────────────────────────┐
28
+ │ Topbar (3-kol grid, LEAF-anpassad) │
29
+ │ col 1: pwa-avatar │
30
+ │ col 2: topbar__logo "Min sida" │
31
+ │ col 3: <span class="avatar"> utan link │ ← LEAF
32
+ ├────────────────────────────────────────────┤
33
+ │ Profilkort (.profile-panel) │
34
+ │ · avatar--lg │
35
+ │ · display-name med inline-edit (om │
36
+ │ multi-user) eller skippa (single-user) │
37
+ │ · email (.text-caption eller .text-meta) │
38
+ ├────────────────────────────────────────────┤
39
+ │ Panel "Att åtgärda" CONDITIONAL │ ← Alerts först
40
+ │ panel--attention │
41
+ │ setting-rows med .panel__pill (count) │
42
+ ├────────────────────────────────────────────┤
43
+ │ Panel "Min [domän]" OPTIONAL │ ← om domän-data finns
44
+ │ setting-rows som öppnar field_sheet │
45
+ ├────────────────────────────────────────────┤
46
+ │ Panel "Mina inställningar" │
47
+ │ setting-rows + toggles + sheets │
48
+ ├────────────────────────────────────────────┤
49
+ │ Panel "Mina vyer" │
50
+ │ setting-rows som länk-rows │
51
+ ├────────────────────────────────────────────┤
52
+ │ Panel "Installera [app]" CONDITIONAL │ ← om !standalone PWA
53
+ │ iOS Safari-instruktioner (iPhone-only) │
54
+ ├────────────────────────────────────────────┤
55
+ │ Logout-row │
56
+ │ .btn--logout (paketet) │
57
+ └────────────────────────────────────────────┘
58
+ ```
59
+
60
+ ### Regler
61
+
62
+ 1. **Topbar är 3-kol-grid, LEAF-anpassad.** Avatar i kolumn 3 finns
63
+ visuellt för layout-konsekvens men är `<span class="avatar">` utan
64
+ `<a>`-wrapper. Inte klickbar (användaren är redan på destinationen).
65
+ Topbar__logo blir korrekt centrerat av grid `auto 1fr auto`.
66
+
67
+ 2. **Topbar-text "Min sida"** (svenska). Kort, idiomatisk, konsekvent
68
+ över alla Klodd-appar.
69
+
70
+ 3. **Profilkort är obligatoriskt.** `.profile-panel` (lyft till paketet
71
+ v3.21.0). Avatar--lg + email som minimum. Display-name med inline-edit
72
+ OBLIGATORISKT för multi-user-appar (Ekonom), OPTIONAL för single-user-
73
+ appar (Jubb). Single-user-app utan tydligt display-name-bruk får skippa.
74
+
75
+ 4. **Sektion-ordning är "alerts first".** "Att åtgärda" placeras FÖRE
76
+ "Min [domän]" och övriga settings så användaren direkt ser pending
77
+ tasks vid sidladdning.
78
+
79
+ 5. **"Min [domän]"-panel är OPTIONAL.** Bara om appen har domän-
80
+ specifika personliga värden separat från preferences (Ekonom:
81
+ inkomst + saldo). Single-purpose-appar (Jubb) slår ihop allt till
82
+ "Mina inställningar".
83
+
84
+ 6. **Alla settings använder setting-row + sheet.** Inga inline-knappar
85
+ för actions som ändrar state. Detta inkluderar:
86
+ - Choice-actions: `.sheet__item-grid` med `.sheet__item--primary`
87
+ - Field-editing: `field_sheet` macro
88
+ - Multi-field-editing: `multi_field_sheet` macro
89
+ - Toggles: `setting-toggle` inom setting-row
90
+
91
+ 7. **Conditional panels visas via JS eller Jinja-conditional.** Inte
92
+ alltid synliga, inte alltid dolda. Aktivering-villkor i ADR/template-
93
+ kommentar.
94
+
95
+ 8. **PWA-install-panel är iPhone-only.** Klodd-appar utvecklas för iOS
96
+ PWA + dev-desktop. Android Chrome-instruktioner inkluderas inte
97
+ (skulle ge falskt-positiva intryck att Android stöds). Detection
98
+ via `window.matchMedia('(display-mode: standalone)').matches`.
99
+
100
+ 9. **Logout är paketet `.btn--logout` i `.logout-row`** (v3.21.0).
101
+
102
+ ### Domain-naming-konvention per app
103
+
104
+ | App | "Min [domän]" eller "Mina inställningar" |
105
+ |---|---|
106
+ | Ekonom | Min ekonomi (inkomst, saldo) + Mina inställningar (toggles, abonnemang) |
107
+ | Jubb | Skip "Min [domän]" → Mina inställningar (Sökläge, Notiser, AI-profil) |
108
+ | Future-app | "Min [domän]" om personliga värden finns separat |
109
+
110
+ ## Konsekvenser
111
+
112
+ **Bra:**
113
+ - Single source of truth för settings-hubbar
114
+ - Future-Margevo + framtida appar får gratis-pattern
115
+ - A11y + iOS-konvention: heading-hierarki + setting-row-rytm
116
+ - Komposit av befintliga paket-komponenter (ingen ny CSS behövs)
117
+
118
+ **Trade-offs:**
119
+ - Single-user-appar tappar ett potentiellt personlighet-element (display-
120
+ name) - acceptabelt eftersom inline-edit av single-user-namn är
121
+ cosmetic, inte funktionellt.
122
+ - "Min sida" är längre än iOS-konvention "Mitt" - men matchar Calle's
123
+ preferens (mer explicit).
124
+
125
+ ## Migration 2026-05-13
126
+
127
+ **Jubb:**
128
+ - /min-sida-template omskriven enligt doktrinen
129
+ - Topbar: lägg avatar i kolumn 3 (`<span class="avatar">` utan link)
130
+ - "Att åtgärda" CONDITIONAL panel: pending triage-jobb med relevance >= 7.0
131
+ - Slå ihop "Sökläge" + "Notiser" + "AI-profil-förslag" till "Mina
132
+ inställningar"-panel
133
+ - "Installera Jubb": bara iOS Safari-instruktioner
134
+
135
+ **Ekonom:**
136
+ - /jag topbar: byt "Mitt" → "Min sida"
137
+ - Topbar: lägg `<span class="avatar">` i kolumn 3 (visuell paritet)
138
+ - Flytta "Att åtgärda" till FÖRE "Min ekonomi" (alerts first)
139
+ - Lägg "Installera Ekonom" CONDITIONAL panel (paritet, iOS-only)
140
+
141
+ ## References
142
+
143
+ - ADR 0011-strikt-bem-elementsyntax.md (besläktad - BEM-disciplin)
144
+ - ADR 0013-visual-coherence-doctrine.md (besläktad - visuell rytm)
145
+ - ADR 0015-panel-title-tag-doctrine.md (besläktad - heading-semantik)
146
+ - 02-components.md profile-panel-entry (v3.21.0)
147
+ - DESIGN-LANGUAGE.md sektion 13 (topbar-layout) + sektion 17 (Mina sidor)
@@ -376,6 +376,160 @@ universell PWA-refresh-action via tap på vänster avatar.
376
376
 
377
377
  **Etablerad 2026-05-13.** Refaktor från flex till grid samma datum.
378
378
 
379
+ ## 14. CSS Specificity-disciplin (modifier-vinst)
380
+
381
+ **Regel:** default/base-regler för komponenter får INTE sätta `color`,
382
+ `font-size`, `font-weight`, `background`, `border`, `text-transform`
383
+ eller `letter-spacing` på item-level när modifier-classes existerar
384
+ för samma property.
385
+
386
+ **Default-properties ska sättas på block-roten** (`.X`), inte på
387
+ item-rule. Item-rader ärver via cascade, modifier-classes (0,1,0)
388
+ override:ar utan specificity-konflikt.
389
+
390
+ **RÄTT (kv-list 3.19.3):**
391
+ ```css
392
+ .kv-list {
393
+ color: var(--text-subtle); /* default på block-root */
394
+ font-size: var(--fs-13);
395
+ }
396
+
397
+ .kv-list > li:not(--budget):not(--divider) {
398
+ display: flex;
399
+ /* INGEN color/font-size här */
400
+ }
401
+
402
+ .kv-list__item--strong { /* 0,1,0 vinner via cascade */
403
+ color: var(--text-default);
404
+ font-weight: var(--fw-medium);
405
+ }
406
+ ```
407
+
408
+ **FEL (kv-list 3.19.1-3.19.2 - tyst bugg):**
409
+ ```css
410
+ .kv-list > li:not(--budget):not(--divider) {
411
+ color: var(--text-subtle); /* 0,3,1 - för hög! */
412
+ font-size: var(--fs-13);
413
+ }
414
+
415
+ .kv-list__item--strong { /* 0,1,0 - vinner inte mot 0,3,1 */
416
+ color: var(--text-default); /* förlorar */
417
+ font-weight: var(--fw-medium); /* OK - ingen konflikt */
418
+ }
419
+ ```
420
+
421
+ Tyst bugg: modifier-classes vinner DELVIS (font-weight) men förlorar
422
+ på color/font-size. Visuell skillnad försvinner. Ingen runtime-error.
423
+
424
+ Se ADR 0014 för fullständig analys + alternativa lösningar (`:where()`,
425
+ modifier-specificity-boost).
426
+
427
+ **Etablerad 2026-05-13** efter kv-list-specificity-incidenten.
428
+
429
+ ## 15. Sentence-case för labels (svenska)
430
+
431
+ **Regel:** label- och header-style text använder sentence-case
432
+ ("Din dagsbudget"), inte uppercase ("DIN DAGSBUDGET"). Visuell
433
+ hierarki kommer från font-size + weight + color, inte caps.
434
+
435
+ **Sentence-case (default):**
436
+ - `.hero__label`, `.panel__title`, `.form-section__title`
437
+ - `.sheet__title`, `.hub-category`, `.sub-section-header`
438
+ - Banner-headings, empty-state titlar, card-rubriker
439
+
440
+ **Uppercase behålls (etablerad konvention):**
441
+ - `.heading-4`, `.text-tiny` (semantiska typo-klasser, dokumenterade
442
+ som "micro-tracker")
443
+ - `.table th` (tabellhuvuden)
444
+ - `.swipe-decision-overlay` (action-overlays SPARA/FÖRVISA)
445
+ - `.avatar` (initialer "CE")
446
+ - `.panel__step-badge`, `.sheet__field-label--upper` (explicit modifier)
447
+ - Styleguide-element i `/design` (visuell hierarki via caps)
448
+
449
+ **Rationale:** svensk grafisk konvention. Calle 2026-05-13: "bara
450
+ capital letter och sen gemener, som vanligt." Matchar iOS Settings,
451
+ Apple HIG, modern app-design. Caps reserveras för badges/actions/
452
+ metadata där tighet + kort längd gör det läsbart.
453
+
454
+ **Letter-spacing:** sentence-case-text använder `var(--ls-tight)`,
455
+ inte `--ls-wider` eller hårdkodad 0.04-0.12em (det är designat för
456
+ uppercase-tracking och ser sträckt ut i sentence-case).
457
+
458
+ **Etablerad 2026-05-13** efter uppercase-audit över paketet.
459
+
460
+ ## 16. HTML-tagg för `.panel__title`
461
+
462
+ **Regel:** panel-rubriker använder `<h3 class="panel__title">` som
463
+ default-tagg. Två specialfall behålls:
464
+
465
+ | Kontext | Tagg | Skäl |
466
+ |---|---|---|
467
+ | Default | `<h3>` | A11y-semantik, screen-reader-navigerbar |
468
+ | Inom `.panel__step-row` | `<span>` | Compound badge+title som en konceptuell rubrik (Steg 1: X) |
469
+ | Inom `<details>` | `<summary>` | Native HTML-krav (summary måste vara första child) |
470
+
471
+ **Definition av `.hero__label` vs `.panel__title`:**
472
+ - `<p class="hero__label">` ENDAST om följt av `<p class="hero-amount">`
473
+ eller `<p class="hero__amount--card">`. Klassen är "labeltext OVANFÖR
474
+ en stor siffer-display".
475
+ - Allt annat (rubrik för list, table, form, text-content, knapp) ska
476
+ vara `<h3 class="panel__title">`.
477
+
478
+ **Förbjudet:**
479
+ - `<div class="panel__title">` (saknar heading-semantik)
480
+ - `<p class="panel__title">` (paragraph är fel semantik för rubrik)
481
+ - `<h3 class="hero__label">` (mismatch klass/tagg)
482
+
483
+ **Rationale:** `<div>` är aria-tree-osynligt - screen readers kan inte
484
+ navigera mellan paneler via heading-shortcut. `<h3>` är industri-praxis
485
+ för card-titles (Radix UI, Material UI, Chakra). CSS oförändrad
486
+ eftersom `.panel__title`-stylling targetar klassen, inte taggen.
487
+
488
+ **Heading-hierarki:** vyer med `<h1 class="heading-1">` får h1→h3
489
+ (legitimt skip). Vyer utan h1 får h3 som top-level (icke-ideal men
490
+ inte brott - browser-tree accepterar skipping levels).
491
+
492
+ **Etablerad 2026-05-13** efter panel__title-audit över Jubb + Ekonom.
493
+ Se ADR 0015 (panel-title-tag-doctrine).
494
+
495
+ ## 17. "Mina sidor"-doktrin (settings-hub-pattern)
496
+
497
+ **Regel:** alla Klodd-appar har en gemensam "Mina sidor"-route med
498
+ locked template-struktur. Route-URL kan variera per app (`/jag` i
499
+ Ekonom, `/min-sida` i Jubb) men UI-pattern är konsekvent.
500
+
501
+ ### Template-struktur (kanonisk ordning)
502
+
503
+ 1. **Topbar** - 3-kol-grid med LEAF-anpassning: pwa-avatar + "Min sida" + `<span class="avatar">` utan link
504
+ 2. **Profilkort** - `.profile-panel` med avatar--lg + ev. display-name med inline-edit + email
505
+ 3. **"Att åtgärda"** CONDITIONAL - `panel--attention` med setting-rows + `.panel__pill`
506
+ 4. **"Min [domän]"** OPTIONAL - app-specifika personliga värden
507
+ 5. **"Mina inställningar"** - app-agnostiska preferences via setting-rows + sheets
508
+ 6. **"Mina vyer"** - navigation till sub-vyer
509
+ 7. **"Installera [app]"** CONDITIONAL - iPhone-only PWA-instruktioner
510
+ 8. **Logout-row** - `.btn--logout`
511
+
512
+ ### Kritiska regler
513
+
514
+ - Topbar avatar i kolumn 3 är `<span class="avatar">` UTAN `<a>`-wrapper på Min sida (LEAF-pattern - användaren är redan på destinationen, men element kvar för layout-konsekvens)
515
+ - Topbar-text alltid **"Min sida"** över alla appar (svenska, kort)
516
+ - Alla settings använder **setting-row + sheet** - inga inline-knappar för state-changes
517
+ - "Att åtgärda" placeras **FÖRE** "Min [domän]" så alerts visas direkt
518
+ - Display-name **obligatoriskt** för multi-user-appar (Ekonom), **optional** för single-user (Jubb)
519
+ - "Min [domän]" är **OPTIONAL** panel - bara om appen har domän-specifika personliga värden
520
+ - PWA-install-panel **bara iOS Safari**-instruktioner (Klodd-appar är iPhone-targeted)
521
+
522
+ ### Förbjudet
523
+
524
+ - Inline-knapp för push-aktivering (måste vara setting-row)
525
+ - Topbar utan kolumn 3-avatar på Min sida (bryter grid-centrering)
526
+ - Topbar utan LEAF-anpassning (avatar måste vara `<span>`, inte `<a>` på destinationen)
527
+ - Android Chrome-instruktioner i PWA-install-panel (klodd-appar är iPhone-only)
528
+
529
+ **Etablerad 2026-05-13** efter audit-jämförelse mellan Ekonoms `/jag`
530
+ och Jubbs `/min-sida`. Se ADR 0016 (mina-sidor-doctrine) för fullständig
531
+ specifikation + locked rationale.
532
+
379
533
  ## 12. När ändra regler
380
534
 
381
535
  Denna fil är **locked**. Ändringar kräver: