tamga-ui 0.4.3 → 0.5.0

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.
Files changed (204) hide show
  1. package/CHANGELOG.md +908 -0
  2. package/README.md +3 -1
  3. package/dist/blocks/count-row.d.ts +3 -8
  4. package/dist/blocks/count-row.d.ts.map +1 -1
  5. package/dist/blocks/count-row.js +3 -8
  6. package/dist/blocks/filter-bar.d.ts +42 -28
  7. package/dist/blocks/filter-bar.d.ts.map +1 -1
  8. package/dist/blocks/filter-bar.js +13 -5
  9. package/dist/blocks/index.d.ts +4 -17
  10. package/dist/blocks/index.d.ts.map +1 -1
  11. package/dist/blocks/index.js +4 -17
  12. package/dist/blocks/save-bar.d.ts +4 -12
  13. package/dist/blocks/save-bar.d.ts.map +1 -1
  14. package/dist/blocks/save-bar.js +4 -12
  15. package/dist/components/a11y.d.ts.map +1 -1
  16. package/dist/components/a11y.js +4 -15
  17. package/dist/components/advanced-input.d.ts +54 -48
  18. package/dist/components/advanced-input.d.ts.map +1 -1
  19. package/dist/components/advanced-input.js +64 -59
  20. package/dist/components/announcement.d.ts +34 -0
  21. package/dist/components/announcement.d.ts.map +1 -0
  22. package/dist/components/announcement.js +12 -0
  23. package/dist/components/appearance.d.ts +33 -1
  24. package/dist/components/appearance.d.ts.map +1 -1
  25. package/dist/components/appearance.js +78 -7
  26. package/dist/components/avatar.d.ts +28 -21
  27. package/dist/components/avatar.d.ts.map +1 -1
  28. package/dist/components/avatar.js +46 -22
  29. package/dist/components/badge.d.ts +54 -4
  30. package/dist/components/badge.d.ts.map +1 -1
  31. package/dist/components/badge.js +42 -9
  32. package/dist/components/bar.d.ts.map +1 -1
  33. package/dist/components/bar.js +4 -20
  34. package/dist/components/button.d.ts +15 -7
  35. package/dist/components/button.d.ts.map +1 -1
  36. package/dist/components/button.js +54 -8
  37. package/dist/components/chart.d.ts +5 -0
  38. package/dist/components/chart.d.ts.map +1 -1
  39. package/dist/components/chart.js +18 -11
  40. package/dist/components/checkbox.d.ts +23 -1
  41. package/dist/components/checkbox.d.ts.map +1 -1
  42. package/dist/components/checkbox.js +3 -4
  43. package/dist/components/chrome.d.ts +38 -54
  44. package/dist/components/chrome.d.ts.map +1 -1
  45. package/dist/components/chrome.js +55 -63
  46. package/dist/components/combobox.d.ts.map +1 -1
  47. package/dist/components/combobox.js +19 -3
  48. package/dist/components/data-table.d.ts +4 -4
  49. package/dist/components/data-table.d.ts.map +1 -1
  50. package/dist/components/data-table.js +15 -16
  51. package/dist/components/date-picker.d.ts.map +1 -1
  52. package/dist/components/date-picker.js +11 -4
  53. package/dist/components/disclosure.d.ts +17 -2
  54. package/dist/components/disclosure.d.ts.map +1 -1
  55. package/dist/components/disclosure.js +4 -8
  56. package/dist/components/display.d.ts +119 -94
  57. package/dist/components/display.d.ts.map +1 -1
  58. package/dist/components/display.js +103 -106
  59. package/dist/components/empty-state.d.ts +70 -52
  60. package/dist/components/empty-state.d.ts.map +1 -1
  61. package/dist/components/empty-state.js +40 -52
  62. package/dist/components/error-state.d.ts +21 -4
  63. package/dist/components/error-state.d.ts.map +1 -1
  64. package/dist/components/error-state.js +16 -6
  65. package/dist/components/file-upload.d.ts +45 -2
  66. package/dist/components/file-upload.d.ts.map +1 -1
  67. package/dist/components/file-upload.js +32 -7
  68. package/dist/components/gauge.d.ts +72 -9
  69. package/dist/components/gauge.d.ts.map +1 -1
  70. package/dist/components/gauge.js +52 -49
  71. package/dist/components/icons.d.ts +1 -1
  72. package/dist/components/icons.d.ts.map +1 -1
  73. package/dist/components/icons.js +4 -0
  74. package/dist/components/input.d.ts +21 -0
  75. package/dist/components/input.d.ts.map +1 -1
  76. package/dist/components/input.js +15 -1
  77. package/dist/components/layout.d.ts +47 -70
  78. package/dist/components/layout.d.ts.map +1 -1
  79. package/dist/components/layout.js +31 -73
  80. package/dist/components/link.d.ts +5 -10
  81. package/dist/components/link.d.ts.map +1 -1
  82. package/dist/components/live-scope.d.ts.map +1 -1
  83. package/dist/components/live-scope.js +5 -8
  84. package/dist/components/log-view.d.ts +32 -1
  85. package/dist/components/log-view.d.ts.map +1 -1
  86. package/dist/components/log-view.js +42 -15
  87. package/dist/components/number-input.d.ts +20 -2
  88. package/dist/components/number-input.d.ts.map +1 -1
  89. package/dist/components/number-input.js +32 -9
  90. package/dist/components/overlay.d.ts +69 -61
  91. package/dist/components/overlay.d.ts.map +1 -1
  92. package/dist/components/overlay.js +134 -77
  93. package/dist/components/pagination.d.ts.map +1 -1
  94. package/dist/components/pagination.js +12 -7
  95. package/dist/components/pie.d.ts.map +1 -1
  96. package/dist/components/pie.js +8 -7
  97. package/dist/components/primitives.d.ts +136 -6
  98. package/dist/components/primitives.d.ts.map +1 -1
  99. package/dist/components/primitives.js +73 -20
  100. package/dist/components/progress.d.ts +44 -9
  101. package/dist/components/progress.d.ts.map +1 -1
  102. package/dist/components/progress.js +21 -9
  103. package/dist/components/radio-group.d.ts.map +1 -1
  104. package/dist/components/radio-group.js +11 -12
  105. package/dist/components/rail-link.d.ts +17 -1
  106. package/dist/components/rail-link.d.ts.map +1 -1
  107. package/dist/components/rail-link.js +3 -3
  108. package/dist/components/rich-text.d.ts +8 -1
  109. package/dist/components/rich-text.d.ts.map +1 -1
  110. package/dist/components/rich-text.js +13 -19
  111. package/dist/components/says-bubble.d.ts +38 -0
  112. package/dist/components/says-bubble.d.ts.map +1 -0
  113. package/dist/components/says-bubble.js +6 -0
  114. package/dist/components/scroll-x.d.ts +23 -0
  115. package/dist/components/scroll-x.d.ts.map +1 -0
  116. package/dist/components/scroll-x.js +27 -0
  117. package/dist/components/section.d.ts +18 -1
  118. package/dist/components/section.d.ts.map +1 -1
  119. package/dist/components/section.js +12 -1
  120. package/dist/components/segmented.d.ts +17 -1
  121. package/dist/components/segmented.d.ts.map +1 -1
  122. package/dist/components/segmented.js +26 -6
  123. package/dist/components/skeleton.d.ts.map +1 -1
  124. package/dist/components/skeleton.js +1 -1
  125. package/dist/components/slider.d.ts +16 -2
  126. package/dist/components/slider.d.ts.map +1 -1
  127. package/dist/components/slider.js +12 -5
  128. package/dist/components/sparkline.d.ts +9 -2
  129. package/dist/components/sparkline.d.ts.map +1 -1
  130. package/dist/components/sparkline.js +9 -5
  131. package/dist/components/spinner.d.ts +16 -1
  132. package/dist/components/spinner.d.ts.map +1 -1
  133. package/dist/components/spinner.js +24 -1
  134. package/dist/components/square-picker.d.ts +4 -20
  135. package/dist/components/square-picker.d.ts.map +1 -1
  136. package/dist/components/square-picker.js +4 -20
  137. package/dist/components/stacked-bar.d.ts.map +1 -1
  138. package/dist/components/stacked-bar.js +5 -4
  139. package/dist/components/surface.d.ts +32 -3
  140. package/dist/components/surface.d.ts.map +1 -1
  141. package/dist/components/surface.js +11 -4
  142. package/dist/components/switch.d.ts +14 -1
  143. package/dist/components/switch.d.ts.map +1 -1
  144. package/dist/components/switch.js +2 -3
  145. package/dist/components/tabs.d.ts +35 -11
  146. package/dist/components/tabs.d.ts.map +1 -1
  147. package/dist/components/tabs.js +48 -5
  148. package/dist/components/timeline-strip.d.ts +28 -3
  149. package/dist/components/timeline-strip.d.ts.map +1 -1
  150. package/dist/components/timeline-strip.js +19 -8
  151. package/dist/components/tone.d.ts +51 -14
  152. package/dist/components/tone.d.ts.map +1 -1
  153. package/dist/components/tone.js +76 -14
  154. package/dist/components/tree-select.d.ts.map +1 -1
  155. package/dist/components/tree-select.js +10 -3
  156. package/dist/fonts/LICENSE-jetbrains-mono.txt +93 -0
  157. package/dist/fonts/LICENSE-onest.txt +93 -0
  158. package/dist/fonts/LICENSE-red-hat-display.txt +93 -0
  159. package/dist/fonts/jetbrains-mono-latin-ext-wght-normal.woff2 +0 -0
  160. package/dist/fonts/jetbrains-mono-latin-wght-normal.woff2 +0 -0
  161. package/dist/fonts/onest-latin-ext-wght-normal.woff2 +0 -0
  162. package/dist/fonts/onest-latin-wght-normal.woff2 +0 -0
  163. package/dist/fonts/red-hat-display-latin-ext-wght-normal.woff2 +0 -0
  164. package/dist/fonts/red-hat-display-latin-wght-normal.woff2 +0 -0
  165. package/dist/fonts.css +54 -0
  166. package/dist/index.d.ts +10 -7
  167. package/dist/index.d.ts.map +1 -1
  168. package/dist/index.js +9 -6
  169. package/dist/kit.css +3175 -645
  170. package/dist/lib/color.d.ts +8 -19
  171. package/dist/lib/color.d.ts.map +1 -1
  172. package/dist/lib/color.js +8 -19
  173. package/dist/lib/data-props.d.ts +4 -15
  174. package/dist/lib/data-props.d.ts.map +1 -1
  175. package/dist/lib/data-props.js +4 -15
  176. package/dist/lib/image.d.ts +4 -20
  177. package/dist/lib/image.d.ts.map +1 -1
  178. package/dist/lib/image.js +4 -20
  179. package/dist/lib/palette.d.ts +16 -20
  180. package/dist/lib/palette.d.ts.map +1 -1
  181. package/dist/lib/palette.js +136 -71
  182. package/dist/lib/scroll-lock.d.ts +4 -14
  183. package/dist/lib/scroll-lock.d.ts.map +1 -1
  184. package/dist/lib/scroll-lock.js +4 -14
  185. package/dist/patterns/app-shell.d.ts.map +1 -1
  186. package/dist/patterns/app-shell.js +4 -13
  187. package/dist/patterns/appearance-template.d.ts +20 -15
  188. package/dist/patterns/appearance-template.d.ts.map +1 -1
  189. package/dist/patterns/appearance-template.js +29 -8
  190. package/dist/patterns/list-template.d.ts.map +1 -1
  191. package/dist/patterns/list-template.js +1 -1
  192. package/dist/patterns/overview-template.d.ts.map +1 -1
  193. package/dist/patterns/overview-template.js +1 -1
  194. package/dist/patterns/settings-template.d.ts +1 -12
  195. package/dist/patterns/settings-template.d.ts.map +1 -1
  196. package/dist/patterns/settings-template.js +2 -2
  197. package/dist/patterns/shared.d.ts.map +1 -1
  198. package/dist/patterns/shared.js +8 -1
  199. package/dist/styles.css +2 -1
  200. package/dist/theme.css +96 -259
  201. package/package.json +4 -2
  202. package/dist/components/control-base.d.ts +0 -7
  203. package/dist/components/control-base.d.ts.map +0 -1
  204. package/dist/components/control-base.js +0 -6
package/CHANGELOG.md CHANGED
@@ -5,6 +5,914 @@ Versions follow [semver](https://semver.org/). Through `0.x`, breaking changes m
5
5
 
6
6
  > 🇹🇷 Türkçe için [tamga.org.tr/tr](https://tamga.org.tr/tr).
7
7
 
8
+ ## 0.5.0
9
+
10
+ > The Soft Neo Brutalism port: every component, every token and the physics behind them. The
11
+ > renames are under "Breaking", each with the line that moves you across.
12
+
13
+ ### Removed
14
+
15
+ - **A dead layer of unprefixed classes is gone.** `theme.css` still carried `.press-surface`,
16
+ `.seated-surface`, `.card-surface`, `.sunk-surface`, `.app-surface`, `.gutter`, `.rail-link` and
17
+ a second copy of the skeleton and beacon keyframes (`.animate-skeleton-breath`,
18
+ `.animate-beacon-breath`). All of it predates the `tamga-` rename: the real physics lives in
19
+ `kit.css` as the `.tamga-*` family, and nothing in the kit, the docs site or either consuming
20
+ panel referenced a single one of these. One comment beside them even claimed a block had been
21
+ deleted while the block sat eight lines below it. 210 lines.
22
+
23
+ - **27 token names the kit itself never read.** `--shadow-raised`, `--shadow-pressed`,
24
+ `--shadow-seated-pressed`, `--shadow-overlay`, `--shadow-ambient`, `--shadow-rim`, the ten
25
+ `-foreground` pairs, `--primary`, `--secondary`, `--muted`, `--popover`, `--beacon`, `--input`,
26
+ `--duration-instant`, `--table-min-wide`, `--table-min-widest`, `--color-danger-solid` and
27
+ `--radius-lg`. They were printed on the token page, so a consumer could reasonably read them as
28
+ part of the kit's vocabulary, and the vocabulary was stale: the page listed `--shadow-raised` as
29
+ `2px 2px 0 0 var(--shadow-color)` while the kit actually paints
30
+ `2px 2px 0 var(--color-edge-strong)`. A name the kit does not read is a name nothing keeps
31
+ honest. The token page goes from 162 entries to 136, and every entry left is one the kit uses.
32
+
33
+ Migration: each has an equivalent in the kit's own family, and in most cases the same value
34
+ (`--primary-foreground` and `--color-accent-ink` were both `#fdfcfa` light / `#12245c` dark).
35
+ Kept: the raw brand ramp (`--color-brand-50/100/400/600`), which is documented as raw material
36
+ that the semantic tokens derive from rather than something the interface consumes.
37
+
38
+ - **19 `@theme inline` bridges nobody could reach.** `--color-background`, `--color-foreground`,
39
+ `--color-primary`, `--color-muted`, `--color-popover`, `--color-border`, `--color-input`, the
40
+ `-foreground` pairs and the rest of the shadcn-compat naming existed only to generate Tailwind
41
+ utilities (`bg-background`, `text-muted-foreground`, …). Nothing used them, and they were not on
42
+ the token page either, so they were not an offering to consumers, just weight: **266 fewer
43
+ generated utilities** (1401 → 1135). The raw tokens behind them are untouched, so a product that
44
+ writes `var(--primary)` in its own CSS still works.
45
+
46
+ Kept on purpose: `--font-sans`. No rule in the kit reads it, but Tailwind's preflight does, so
47
+ removing it would have quietly changed the typeface of every consumer.
48
+
49
+ ### Added
50
+
51
+ - **`DropdownMenu` takes `defaultOpen`.** A documentation surface has to be able to show the open
52
+ panel; until now the only way was a click, so a menu on a poster page was an empty button. Not
53
+ for a product screen: a menu that is open before anyone asked for it covers what is under it.
54
+
55
+ ### Changed
56
+
57
+ - **`FileUpload`'s drop zone is no longer squared paper.** It reused `.tamga-art-well`, a ground
58
+ designed for ILLUSTRATIONS ("an instrument on paper"); behind a sentence and two lines of hint
59
+ text the 8px ruling competes with the words. The well now has its own class
60
+ (`.tamga-drop-well`): a dashed edge on the accent's palest ground, which is the quietest way to
61
+ say "you can drop something here". `.tamga-art-well` is unchanged and still draws illustrations.
62
+
63
+ ### Fixed
64
+
65
+ - **`Calendar` now opens on the month of its `value`.** It always opened on the current month, so
66
+ a date handed to it from another month left the grid showing today with no selection anywhere in
67
+ sight: the caller had given a date and the calendar appeared to have ignored it. With no `value`
68
+ it still opens on today.
69
+
70
+ ### Changed
71
+
72
+ - **`ColorSwatches`: the free-colour square now carries an eyedropper.** Its empty state was a
73
+ checkered ground and nothing else. The ground says "no colour here" but not what the square is
74
+ FOR, and this square does something none of the other six do: it opens a colour picker. The
75
+ glyph says that. Once a free colour is chosen the eyedropper gives way to the check mark the
76
+ presets use, so the "which one is selected" reading stays the same across all seven.
77
+
78
+ - **The chart family now matches the design reference.** Eight components changed, all of them
79
+ visual: the bar chart's rows fade to 0.4 around the one you point at (a painted row background
80
+ turned five rows into five boxes), the pie's first series takes the accent instead of the
81
+ categorical palette's olive, the score ring is a filled arc rather than a 32-segment dial, the
82
+ score meter became a five-band scale with a pointer rather than a filling bar, and progress
83
+ moved its label and number above the track and dropped the ticks that used to stand in for the
84
+ number.
85
+
86
+ Two rules came out of it and they hold across the family: **the first series is the accent**
87
+ (`seriRenk`), because most charts have one series and it is the page's real information; and
88
+ **emphasis is opacity, never a painted ground**.
89
+
90
+ The sparkline lost its area fill and baseline: both gave an axis-less line the look of an axis,
91
+ promising a scale it cannot deliver. It also lost its end marker by default (`mark` turns it
92
+ back on), because in a column of sparklines it repeats on every row.
93
+
94
+
95
+ - **The form family now matches the design reference.** Nine components, and each change answers a
96
+ complaint someone actually had: the combobox now **bolds the matched letters** in each option, so
97
+ a long list shows you why a row is in it; the date picker's cells are a 36px grid with a framed
98
+ today instead of text in a table; multi select shows **four chips and then `+N`**, because a field
99
+ holding eleven selections used to grow taller than the form; tags input asks twice before
100
+ destroying — the first Backspace marks the last tag, the second removes it; and tree select draws
101
+ a **mixed** parent when only some children are picked, instead of an unchecked one that lies.
102
+
103
+ Two of them are rebuilds rather than edits. **Rich text's active button is a wash, not a fill**:
104
+ three formats can be on at once, and three filled buttons broke the one-primary-action rule and
105
+ made the toolbar read like a warning. **Schedule input** is described under Breaking.
106
+
107
+ Number input keeps its own rule: while the field has focus it shows **what you typed**, and
108
+ formats only on blur. Formatting mid-keystroke moves the caret out from under the cursor, which is
109
+ how "1.500" becomes "1.5001" — measured, not imagined.
110
+
111
+
112
+ - **The table and list family now matches the design reference.** `SortHeader` draws a neutral
113
+ two-way glyph on sortable columns that are not sorted, and keeps the **direction** arrow for the
114
+ sorted one, now in the accent colour. Before this, an unsorted header carried nothing at all, so
115
+ nothing on screen said the header was clickable and sorting was found by accident.
116
+
117
+ `SelectAll` stopped drawing its own box. It was a `role="checkbox"` span that painted a plain
118
+ square when checked, while the row boxes beside it painted `Checkbox`'s tick: two different
119
+ marks in one column, visible only when everything was selected. It is a `Checkbox` with
120
+ `indeterminate` now — same component, same size, same keyboard.
121
+
122
+ `ListRow` highlights on `--color-hover`, the token the table already used (it was on
123
+ `--color-sunk`, so a list beside a table showed two different greys), and it takes `min-height`
124
+ rather than `height`, because a row with a title *and* a sub-line was overflowing its box.
125
+
126
+ `LogView` gained level badges and dashed row rules: 13px mono at a 1.7 line height, a fixed
127
+ 78px timestamp column, and a badge whose **slot** is fixed width while the badge hugs its text,
128
+ so the message column starts at the same place on every line.
129
+
130
+
131
+ - **The overlay family now matches the design reference.** `Dialog` enters instead of appearing:
132
+ 160ms, opacity plus a scale from .98 (it settles, it does not grow), with the scrim fading
133
+ alongside it, and `prefers-reduced-motion` turning both off while leaving the dialog open.
134
+
135
+ **The scrim is the ink at 45%**, not a warm brown at 32%. The dialog page states the rule —
136
+ everything behind it becomes unreachable — and at 32% the page behind stayed comfortably
137
+ readable, so the dialog read as a card rather than as a gate. Same hue as the ink, so the dim
138
+ is one colour deepening rather than a second colour cast over the page.
139
+
140
+ `Popover` grew an arrow, drawn as two stacked triangles (edge colour under, surface colour 2px
141
+ down), aligned 22px from the trigger's side rather than centred on the 288px panel, because an
142
+ arrow leaving the middle points at empty space instead of at what opened it. `Tooltip` keeps
143
+ its own arrow: that bubble is `fixed` and measured in JS, so it is detached from the trigger's
144
+ box and the arrow is its only tie.
145
+
146
+ `Tooltip` also waits now: **400ms on hover, nothing on keyboard focus**, `Esc` closes it, and
147
+ the label is bound with `aria-describedby` to a copy that is always in the DOM — a screen
148
+ reader reads the description the moment focus lands, while the bubble may not be open yet.
149
+
150
+
151
+ - **The shell family now matches the design reference.** `Tabs` underlines the active tab with
152
+ **3px** sitting on the strip's own rule rather than a 1px line the same weight as it. The rail's
153
+ active row takes an accent **wash**: hover and active were both the surface colour, so the only
154
+ thing separating them was 3px of shadow against 4px, which the eye does not read. It is still a
155
+ wash and not a fill, so "you are here" is still not painted as an action.
156
+
157
+ `LogoTile`'s letter is the display face at its heaviest, in the accent colour. Drawn in muted
158
+ mono it read as a *code* rather than as a brand, and this tile stands in for an image.
159
+
160
+ **Keyboard, and it is a promise.** `role="tablist"` and `role="radiogroup"` announce "move with
161
+ the arrows"; while the arrows did nothing, that role was a lie. Both strips now move with ←/→,
162
+ jump with Home/End, carry focus along with the selection, skip disabled tabs, and expose a
163
+ **single tab stop** — an eight-tab strip used to cost eight presses to get past.
164
+
165
+ **`Segmented` is a radio group**, not a group of pressed buttons: as buttons, a screen reader
166
+ did not say how many choices there were or which one was chosen, because pressed buttons count
167
+ as independent of each other. `RadioGroup` stays a separate component for a separate job — it
168
+ is a form field, with a label, help text and an error line; this is a toolbar control.
169
+
170
+
171
+ - **The design's own kit was read section by section — all 96 of them.** Six reference files
172
+ (actions, form, data, feedback, layout, extras), each section compared against its page and its
173
+ component. What came out of it is in the entries above and below; the ones that changed
174
+ behaviour rather than measurements are marked as such.
175
+
176
+ The appearance screen's five parts (`ColorSwatches`, `ThemeCards`, `RailCards`, `ImageField`,
177
+ `SquarePicker`) were documented in prose with no live example on the page — the one thing a
178
+ screen built out of **pictures instead of words** cannot be explained without. Its page opens
179
+ with the real thing now.
180
+
181
+
182
+ - **An accordion is a stack of cards, and the open one rises.** Every section used to be a row
183
+ inside one surface, separated by rules, and which one was open could only be read from the
184
+ direction of its caret. Each is its own card now (1.5px edge, r7); open, it takes a 3px base
185
+ and a washed head, and its body indents to the title's text column.
186
+
187
+ `SectionHead` grew a second size. `base` stays the card's own strip; **`lg` is a section of a
188
+ PAGE** — a 30px title, an eyebrow above it **in the accent** (in a quiet grey it read as a
189
+ separate line rather than part of the title), a line under it, and no closing rule, because
190
+ the container sets the page's rhythm.
191
+
192
+
193
+ - **Avatars are brand tiles now.** They were grey-on-grey with one 3px corner at every size. The
194
+ **corner grows with the size** (4 · 5 · 7 · 8) — at 24px an 8px corner turns the square towards
195
+ a circle, and at 56 a 4px corner leaves it sharp — the edge is 1.5px like every other pressed
196
+ thing, and **only the large ones (40 up) carry a base**, because a 24px tile lives inside a row
197
+ and a base there crowds it. Two fills, `solid` and `soft`; not a palette, because an avatar is
198
+ not a status.
199
+
200
+ `ErrorState` is **centred** and its retry is the primary button: the block replaces a section
201
+ rather than being pinned to its left edge, and in that state the screen offers nothing else.
202
+
203
+
204
+ - **`Code` is the dark block it was drawn as.** It was a light surface with a copy button parked
205
+ in its corner, over the first line. It is now the inverse surface in both themes, with a strip
206
+ carrying the **filename** and the copy button, and **line numbers** that cannot be selected —
207
+ someone dragging to copy the code was taking the numbers with it. The reference puts an accent
208
+ shadow under it; Law 1 says the edge and the offset are one colour, and the law comes first.
209
+
210
+ `Alert` carries the **tone's glyph in its own tile** (the four feedback marks derive from the
211
+ tone, so the same tone cannot be a triangle on one screen and a circle on the next). Its 3px
212
+ left border is gone: the comment above it had said "the tone is the wash, not the left rule"
213
+ for a long time while the rule kept drawing it.
214
+
215
+
216
+ - **The form family, the same way.** A failing field is **washed**, not only outlined: with four
217
+ fields in a form the eye was hunting for the bad one along the edges. Its error line carries a
218
+ filled warning glyph, because colour alone is not a mark. And **disabled is a different object,
219
+ not a faded one** — a dashed edge on a sunk ground, the rule already written for buttons and
220
+ applied only there; checkbox, radio and switch were fading to 45% opacity instead.
221
+
222
+ `Select` options can carry a `hint`, the quiet second value on the right of a row ("2-3 days",
223
+ "₺49"): under the label it doubles the list's height, and what is being read is still one
224
+ choice.
225
+
226
+
227
+ - **The action family went through the reference one section at a time.** `IconButton` has three
228
+ sizes — 30, 40 and 50 — and **the corner grows with the size** (5 · 7 · 8), because one radius
229
+ across three sizes makes the small one look round and the large one sharp; its base is 3px, not
230
+ 2, which had left it sitting at the same height as the mini button beside it. `MiniButton` is
231
+ 28px, the reference's own measure.
232
+
233
+ Both gained the fills they were missing (`primary`, `soft`, `danger`, `ghost` / `quiet`), and
234
+ **`ghost` is not an object at rest**: no edge, no base, it appears under the pointer. Thirty
235
+ rows each carrying a three-dot button would otherwise be thirty objects.
236
+
237
+ `Steps` marks the current step with a **light accent fill** rather than an outline: the done
238
+ ones are filled dark, and an outlined current step read as one of the empty ones ahead of it.
239
+ `Kbd` is layered now (1.5px edge, 2px base) — in this kit depth is the mark of what can be
240
+ pressed, and a shortcut describes something to press.
241
+
242
+
243
+ - **Measured against the reference, component by component.** `Segmented` is 7px around the well
244
+ and 5px around each button (it was 6 and 4), pads 6×14, and keeps **full ink on the unselected
245
+ options**: a faded option read as *disabled*, while what separates them is the seat. It also
246
+ gained the **6px gap between a glyph and its word** that it never had, which is why every
247
+ icon-bearing segment glued the two together.
248
+
249
+ `Tabs` underlines the **body** of a tab, not the word: 4px between tabs with 8×12 padding
250
+ inside each, rather than 24px between bare words. `Kpi` follows the reference's two shapes (see
251
+ Added), and `StackedBarChart` draws its slices in **one hue's ramp** instead of the categorical
252
+ palette: stacked slices are parts of ONE measure, and in four different colours each slice
253
+ reads as a measure of its own while the total disappears. Each slice keeps a 1px edge with no
254
+ bottom border, so two slices share one line.
255
+
256
+ **`KpiGrid` asks its container, not the screen.** The rule was three media queries plus a count
257
+ of the tiles, and the measured failure was this: on a 1494px screen, inside a 790px column, it
258
+ opened four columns, each tile fell to 190px and the number was clipped. It is now
259
+ `auto-fit` with a 210px floor. `SkeletonKpi`'s bars gained `max-w-full` for the same reason: a
260
+ skeleton that overflows its card is not carrying the geometry it stands in for.
261
+
262
+ **The slider's handle sits on its rail.** WebKit aligns the thumb to the TOP of a track with an
263
+ explicit height rather than centring it, so the square hung below the rail by half its height.
264
+
265
+
266
+ - **The default brand is `#1E4FD8`.** The palette generator's reference brand was already this
267
+ blue, but the default theme still shipped the older, greyer `#2069c9` — so a product that picked
268
+ the very colour the kit is built around got a different palette than the kit's own default. The
269
+ accent family moves with it in both themes; in dark the hue shifts to match while every value
270
+ keeps its lightness, because each of those lightness numbers has a written argument beside it.
271
+
272
+ The selected-row ground moves with it and this one is worth saying out loud: `#ecedf3` had a
273
+ chroma of 0.008 — all but colourless — so a selected row sat one step lighter than a hovered one
274
+ and the two did not read apart. It is `#dce9fb` now: what separates them is hue, not lightness.
275
+ White ink on the primary fill goes from 5.22:1 to 6.47:1.
276
+
277
+ ### Added
278
+
279
+ - **`Announcement`, the strip across the top of a page.** The design's `page-band` section is an
280
+ announcement strip; this kit's `PageBand` is the page's own title block (eyebrow, `h1`,
281
+ subtitle, actions). Two different things under one name, so the strip became its own component.
282
+ Two looks, and the difference is a sentence rather than a style: `loud` is the filled accent and
283
+ it LIFTS, because the reader is meant to act on it and in this kit a fill already means action;
284
+ `quiet` is a wash with no base, because a standing condition ("you are in test mode") only asks
285
+ to be read. Its ink is `--color-accent-ink` in both themes rather than the design's light blue:
286
+ `--color-accent-soft` is fixed across themes while `--color-accent` lightens at night, and the
287
+ pair measures 4.03 in light but **1.61** in dark. Hierarchy comes from size and weight instead.
288
+
289
+ - **`Spinner` gained the two looks the design shows and the kit was missing:** `dots`, three equal
290
+ squares blinking in turn for the side of a line of text, and `square`, a raised box turning on
291
+ its own axis with its shadow, for a wait that owns the view. `bars`, `pixels` and `ring` are
292
+ unchanged, and `bars` stays the default.
293
+
294
+ - **`Avatar` takes a `status` mark.** A square in the tile's bottom-right corner in the tone's own
295
+ colour, with a ring in the ground behind it. It is the one place colour is allowed on an avatar:
296
+ the tile stays monochrome because identity is text, and this mark is not identity but health.
297
+ No default · a mark on every avatar would be a legend nobody wrote.
298
+
299
+ - **`Dot` takes a `size`** (`sm` · `base` · `lg`). The large one carries an edge and a base,
300
+ because at 14px a flat square reads as a swatch rather than a status; the small one drops them,
301
+ because at 6px an edge swallows the square.
302
+
303
+ - **`RichText` takes `hint`**, the quiet mono note at the end of the toolbar saying what the
304
+ editor accepts ("Markdown supported"). The toolbar also moved onto the header strip's ground
305
+ and took the box's own edge weight under it: on the sunk ground it read as a second box inside
306
+ the editor.
307
+
308
+ - **`TimelineStrip` takes `steps`**, the second shape of the same question. The buckets measure a
309
+ RUN (24 hours, one health each); the steps name the STATIONS (an order's history): icon tiles
310
+ on a line, with the line drawn twice · a quiet one end to end and an accent one up to where it
311
+ stands now. Only the steps that happened are raised, because a base under something that has
312
+ not happened read as if it had.
313
+
314
+ - **`ThemeToggle variant="segmented"` can offer two.** It demanded all three labels and threw
315
+ otherwise. The three-way stays the recommendation and the reason stands (see `preference`:
316
+ "system" is the absence of a choice, not a third shade), but offering two is a legitimate
317
+ product decision, so the step now follows the label: no `system` label, no `system` step. With
318
+ two the labels are visible; with three only the glyph is, because three words stretch the
319
+ control past any toolbar.
320
+
321
+ - **`TimelineStrip` takes `className`**, like every other surface in the kit.
322
+
323
+ - **`Progress` takes two more looks.** `blocks` is a row of cells for a COUNTABLE capacity, where
324
+ the real sentence is "7 of 10" and a smooth bar would claim a precision that does not exist;
325
+ the last filled cell takes the light tone, the one mark that says where we are. `split` is one
326
+ track cut into shares, for a distribution that adds up to a whole, with its legend underneath.
327
+ The caller writes the legend line · the kit formats no percentages, because the sign and its
328
+ place are a locale.
329
+
330
+ - **`PasswordInput` takes `strength`**, the meter under the field: so many bars lit out of four,
331
+ and a line saying what is still missing. The kit LIGHTS the bars and does not judge the
332
+ password: what counts as strong is a policy, and a policy belongs to the product.
333
+
334
+ - **`NumberInput` takes `look="quantity"`**, the joined control with the buttons flanking the
335
+ value, for a count that is CLICKED into place. The value there is not an editable field: with
336
+ two buttons doing the whole job, a typed invalid value has nothing to gain. `field` (the text
337
+ field with the stepper on its edge) stays the default, for numbers that are typed.
338
+
339
+ - **`LogView` takes `filters`**, the level bar over the stream. The kit draws the bar and marks
340
+ the chosen one; which levels exist, what they are called and the filtering itself stay with the
341
+ caller · a log view that filtered its own lines would have to know what a level means.
342
+
343
+ - **`FileUpload` takes `files`**, the transfer rows under the well: one going up (a bar and a
344
+ percentage), one that arrived (its size, written by the caller · the kit formats no bytes), one
345
+ refused (the row turns critical and carries the reason beside the file name, not under it).
346
+ `labels.hint` adds the quiet line about accepted types, and the well's glyph now sits in a
347
+ raised tile: a bare glyph in the middle of a dashed area sinks into it.
348
+
349
+ - **`EmptyNote` takes `icon`**, and with it the note collapses to a single line: a card that is
350
+ empty inside an otherwise full screen deserves a sentence, not a picture.
351
+
352
+ - **`EmptyTile` takes `icon`**, which turns the tile into the grid's empty cell · dashed and
353
+ groundless while it waits, solid and raised under the pointer. The dashed edge says "not
354
+ content yet", the lift says "this is a control".
355
+
356
+ - **`Label` takes `look="section"`**, the heading over a group of fields: letter-spaced and
357
+ heavier, because it names a region rather than annotating a number.
358
+
359
+ - **`Select` options take a `label`**, for when the stored value is not the text to show: a locale
360
+ is kept as `tr` and read as "Türkçe".
361
+
362
+ - **`StatusChip` takes a `look`.** `wash` (the default, unchanged) has no edge, because in a
363
+ table of thirty rows an edge on every chip turns the status column into a column of boxes.
364
+ `outline` carries the tone as a line on the surface, for a chip standing alone. `solid` is the
365
+ tone's plate with its own ink, for a TERMINAL state that ends the row's story ("delivered",
366
+ "returned"); it stands in tension with Law 2, a fill means action, which is exactly why it is
367
+ opt-in and not the default.
368
+
369
+ - **`Beacon` takes `edged`**, for a mark that sits ON a control: it grows a step (12 → 14) and
370
+ takes the pressed edge, and its fill turns into the tone's wash. A solid square touching a
371
+ button's own edge reads as part of that edge. The beacon's own size also went from 8px to 12px
372
+ · 8 is the measure of a dot beside a line of text, not of a mark that has to be found across a
373
+ screen.
374
+
375
+ - **`Beacon` takes `look="ping"` and a `state`.** `pulse` (the existing breath) says a STATE: it
376
+ is live, it is streaming. `ping`, a ring expanding out of the mark and fading, says a CALL:
377
+ "there is something new here" · which is what the design attaches to a control's corner. The
378
+ core stays under the ring, otherwise the mark disappears at the faint end of the beat.
379
+
380
+ - **`Badge` takes `dot`**, a mark with no number, for when the answer is "there is something new"
381
+ rather than "there are four". `count` is now optional, since a dot has none to show.
382
+
383
+ - **`Separator` takes `look` and `label`.** `plain` is the quiet rule, `dashed` a provisional one,
384
+ `strong` the one that separates two SECTIONS and therefore takes the edge colour rather than the
385
+ line colour, and `label` carries a word in the middle ("or"), which turns the separator into a
386
+ choice rather than a break.
387
+
388
+ - **`SectionHead` takes `size="sub"`**, the row that opens a sub-section INSIDE a card: no band
389
+ behind it, a rule under it, and `meta` becomes a count chip beside the title instead of a label.
390
+
391
+ - **`Button` takes `size="lg"`** (52px, one step up the shadow ladder at 5px). A screen's single
392
+ action: an empty state's call, a wizard's next.
393
+
394
+ - **`Alert` takes `onDismiss` and `dismissLabel`.** Without them an alert is a standing condition
395
+ the reader cannot clear; with them, a message they have finished with. The control is a ghost
396
+ icon button: inside a washed box, a second bordered object competes with the message itself.
397
+
398
+ - **A tone now carries `markInk`, the glyph that reads ON its `mark`** · and it is not one colour
399
+ for all six. See Fixed below for what it repairs.
400
+
401
+ ### Fixed
402
+
403
+ - **`LineChart`'s hover marker rendered as a pill, and the chart showed a stray tooltip.** Two
404
+ costs of the same decision: the `viewBox` is 100×100 with `preserveAspectRatio="none"` so that
405
+ coordinates behave like percentages. A `<rect width={14} height={14}>` drawn inside it measured
406
+ **252 × 22 px** on a 1800×160 chart, and the comment beside it claimed `vectorEffect` kept the
407
+ size — that property preserves stroke width, not geometry. The marker now sits in the HTML layer
408
+ (`.tamga-chart-mark`), positioned in percentages and sized in pixels, so it is square at every
409
+ width. Separately, the accessible name was given with `<title>`, which browsers render as a
410
+ native tooltip: hovering the chart popped a box reading "Kayıt: 14" — with the wrong number, as
411
+ the title always names the LAST value rather than the hovered one. The name is now `aria-label`:
412
+ same sentence for a screen reader, no bubble on screen.
413
+
414
+ - **Eight tokens were declared but never published.** `--font-sans`, `--font-display`,
415
+ `--font-mono`, `--tracking-label` and four radii (`-sm`, `-md`, `-xl`, `-full`) all exist in
416
+ `theme.css`, all are used, and all generate Tailwind utilities — and none of them appeared in the
417
+ token reference. They sit in the bridge `@theme inline` block, which the extractor read only for
418
+ `var(--x)` aliases. The typography page describes three faces; the token page's typography group
419
+ did not carry their names. For a consumer, a name that is not published does not exist. A gate
420
+ now diffs every declaration in a `:root`/`@theme` body against the published list.
421
+
422
+ - **Twenty-two tokens carried no rationale at all**, and the cause was a pattern rather than
423
+ twenty-two oversights: one comment sits above a RUN of declarations (the six `--color-*-inverse`,
424
+ `--chart-2..5`, `--mark-dot` and its radius) and the parser correctly binds it to the first name
425
+ only. All twenty-two are written; the extractor now stops on a token with no rationale. Every one
426
+ of the 145 tokens carries a bilingual one.
427
+
428
+ - **Three token comments described a kit that no longer exists**, and they are published: the
429
+ token page is generated from them. `--font-display` said "headings are the same face, heavier:
430
+ never a separate display cut" while the token itself reads `"Red Hat Display"`, two lines under
431
+ a `--font-sans` comment that already describes three faces. `--font-mono` said "Mono is the same
432
+ face too" and went on to explain that Red Hat Mono was *not* taken "because it would make the kit
433
+ a two-family system" — the token reads `"JetBrains Mono"` and the kit has been a three-family
434
+ system since the port. `--radius-full` said "avatars and the live dot ONLY"; it is read by the
435
+ score ring alone, and fully round lives in three places (`check:yuvarlak` says so in its own
436
+ output). All three rewritten to what the code does.
437
+
438
+ - **`Segmented` was two pixels taller than every control beside it.** `--control` is 40px and the
439
+ input, the button, the select and the icon button all read it; the segment took its height from
440
+ padding and measured 42. On a toolbar the input and the button lined up and the segment did not,
441
+ which is exactly what the measure page promises cannot happen. A fixed height had been tried once
442
+ and rolled back for a good reason: it was given to the button INSIDE the well, and the well then
443
+ added its own 3px padding and 1px edge on top of it and came out at 46. The height belongs to the
444
+ well itself, with the items stretching into it. `-sm` and the icon segment take `height: auto` and
445
+ are unchanged, because standing under 40px on a toolbar is their whole reason to exist. Found by
446
+ the measure page's own rhythm demo; held by a new gate, `check:olcu-hizasi`, which requires all
447
+ eight base controls to declare `height: var(--control)`.
448
+
449
+ - **Every number input turned into an 18px chip.** A class rename landed on a name that was
450
+ already taken: the tab counter became `.tamga-sayi`, which is what `NumberInput` uses for the
451
+ numeral's FACE. Two definitions of the same class, the later one winning, and the field
452
+ inherited the chip's box · 18px tall, chip padding, chip background. Nothing errored: both
453
+ names were defined, so the class-name gate passed too. The counter is `.tamga-sayac` now, and
454
+ `check-css` gained the question that catches this shape: is this class defined twice in the
455
+ same context? It immediately found four more, all real:
456
+
457
+ - `.tamga-collapsible-head` and `-body` existed twice (a row-shaped port and a card-shaped one)
458
+ and the later pair had quietly dropped the header's `font-weight` and its background
459
+ transition. The design writes 700; the accordion had been shipping unbolded headers.
460
+ - `.tamga-sheet-end` was split across two blocks (mirrored shadow in one, animation in the
461
+ other); they are one block now.
462
+ - `.tamga-eyebrow` was pasted twice, identically.
463
+ - the docs' `.docs-search-btn` carried its radius and height in a second block below itself.
464
+
465
+ - **The popover was drawn as a card, and it showed.** It carried a header strip with its own
466
+ background, a rule under it, a second rule over the footer and a boxed close button · three
467
+ horizontal lines inside a 288px panel, so the eye read the box instead of what was in it. The
468
+ design's popover is one padded surface: a bold title line, the content, a right-aligned footer,
469
+ and nothing else. `closeLabel` is optional now and draws a quiet corner control when given:
470
+ a popover is dismissed with Escape or by clicking away, and the thing that must be closed
471
+ deliberately is a dialog.
472
+
473
+ - **`SecretField` hid its actions inside the field.** Two mini buttons crammed against the right
474
+ edge made copying look like trim. A secret is read, not typed: the value now sits in a sunk
475
+ read-only plate with a key glyph, and reveal and copy are two real buttons beside it, the copy
476
+ one carrying its label as the design writes it.
477
+
478
+ - **A selected radio card only changed its edge.** The design fills it with the soft accent, and
479
+ the note that said "selection by edge, not by the accent colour" was about a different token
480
+ (`--color-accent-line`, which collapses into `--color-edge` on a light brand).
481
+ `--color-accent-soft` is fixed in both themes and carries its own ink, so the wash joins the
482
+ edge and the lift rather than replacing them. `look="card"` also stopped forcing a three-column
483
+ grid: three priced shipping options read down a column, and the container's layout is the
484
+ caller's decision.
485
+
486
+ - **A rising sparkline was grey.** `positive` fell into the muted stroke, which contradicted the
487
+ decision already written on the `Delta` chip: good news is the accent, not green, because a
488
+ rising trend is a movement rather than a state. Neutral stays quiet; a trend line only takes a
489
+ colour when it is carrying good or bad news.
490
+
491
+ - **The number input's operator glyphs were duotone.** `Icon`'s default weight is duotone, which
492
+ is right for an icon that carries the brand and wrong for a control's operator: duotone draws
493
+ its second layer quieter, and a single-stroke mark like a plus or a minus turns into a faded
494
+ sketch of itself. The plus, the minus and the two stepper carets are `bold` now, which is what
495
+ the design writes for them.
496
+
497
+ - **`LocaleSwitcher` drew the browser's `<select>`, not the kit's.** Above two locales it fell
498
+ back to a native control: the OS arrow, the OS dropdown, and none of the kit's rules. It uses
499
+ the kit's `Select` now.
500
+
501
+ - **The separator family was drawn one step off, because the design's token names collide with
502
+ this kit's.** In the design's own file `--edge` is `#0A1F3D`, a near-black navy · this kit's
503
+ `--color-edge` is `#c3c9d3`, a pale grey, and the near-black one is `--color-edge-strong`. Its
504
+ `--line` is our `--color-edge`, and its `--dash` is our `--color-line`. Read as if the names
505
+ matched, the ladder came out flat: the section rule was drawn in pale grey where the design
506
+ asks for near-black. The four rules now sit where they belong · plain `--color-div`, dashed
507
+ `--color-line`, strong `--color-edge-strong` at 1.5px, vertical `--color-edge`. The same
508
+ mis-mapping had reached three more places from this cycle, all corrected: the announcement
509
+ strip's border and base, the spinner's `square`, and the large `Dot`.
510
+
511
+ - **The announcement strip's title was navy on blue.** The kit's base layer paints every
512
+ `strong` with `--color-ink`, which is right for page copy and wrong on a surface carrying its
513
+ own ink: the title measured about 1.9 against the accent ground while the line under it was
514
+ readable. Inside the strip, `strong` inherits now (6.47).
515
+
516
+ - **The `+N` tile in an `AvatarStack` was raised in a different colour from the faces beside
517
+ it.** The avatar tiles lift with `--color-edge-strong`; the summary tile had a pale base, so
518
+ it read as being made of another material.
519
+
520
+ - **In dark, the glyph inside an alert's tile sank into its own plate.** The tile is painted in
521
+ the tone's `mark`, which is a fixed plate in both themes, and the glyph was `--color-page`,
522
+ which is not: near-white in light, near-black at night. On critical it measured **2.96**, under
523
+ the 3.0 floor that applies to a graphical object. One ink cannot fix six plates, so each tone
524
+ now carries its own, picked by measurement: critical takes white (5.89), `info` follows the page
525
+ (7.38 / 8.61) because its plate is the one that flips too, and the rest take the fixed dark
526
+ plate (4.27-7.59). `check-token-contrast` measures these six pairs at the 3.0 glyph floor, and
527
+ it reads the pairs out of `tone.ts` rather than from a list of its own: written by hand, the
528
+ gate passed a deliberate sabotage of the mapping, because what it measured was its own list.
529
+
530
+ - **The `+N` tile in an `AvatarStack` now carries its own base.** It is the one tile in the row
531
+ that is not a person but a summary, and the offset is what steps it out of the row of faces.
532
+
533
+ - **The dropdown menu's group heading was a row-sized label.** It is the name of a GROUP, not of a
534
+ row: caption size, heavy, letter-spaced, with the tighter padding the design gives it (10px
535
+ rather than 16px, which had left a second misalignment down the menu's left edge). Casing stays
536
+ with the caller · the kit does not touch text, and `text-transform` would hand Turkish's i/İ to
537
+ the browser.
538
+
539
+ ### Breaking
540
+
541
+ - **`ScoreMatrix` is a density grid now, not a waffle of one score.** The design's 03.15 is a
542
+ two-dimensional heatmap · rows for one band (a weekday), columns for the other (an hour), and a
543
+ cell's colour reporting the amount at their crossing. What stood under that name here was a
544
+ 10×10 waffle rendering a single 0-100 score, which `ScoreRing` and `ScoreMeter` already answer.
545
+ The new props are `rows`, `columns`, `levels`, `legend` and `cellTitle`; colour comes from the
546
+ brand ramp rather than the tone family, because a cell reports an AMOUNT and not a state, and
547
+ the step is computed against the grid's own maximum · an absolute threshold would make one grid
548
+ impossible to compare with another week's numbers.
549
+
550
+ - **`.tamga-tab-sayi` is now `.tamga-sayac`.** The count chip stopped belonging to tabs when
551
+ `SectionHead size="sub"` started carrying one beside its title, and a class named after tabs in
552
+ that position would have made the name a lie. It was briefly `.tamga-sayi`, which was already
553
+ taken by the numeral face `NumberInput` wears · see the Fixed entry. The `count` prop on `Tabs`
554
+ is unchanged; only a consumer styling the kit's internals by class name is affected.
555
+
556
+ - **In dark, a sunk surface was the page.** `--color-sunk` is the ground for content that sits *in*
557
+ the page rather than on it: filter bars, table headers, code blocks, sub-panels, skeleton
558
+ placeholders. In light it sits ΔL* 7.3 under the page; in dark it sat **1.1** under it, which is
559
+ to say nowhere. The skeleton made it visible: its base is `--color-sunk` and the bar you actually
560
+ see is a breathing `::after` (opacity 0.78 ↔ 0.06), so at the bottom of every breath only the
561
+ base was left, and against the page it measured a ratio of 1.02. The placeholder vanished and
562
+ came back, once per breath. Dark cannot fix this by going lighter (`--color-band` carries that
563
+ measurement: lightening dark surfaces collapsed the card to ΔL* 0.4), so sunk went down instead,
564
+ `#0e1829` → `#020c21`, ΔL* 5.8 under the page. The number is the ramp's own compression, not a
565
+ taste call: dark holds every other surface pair at ~0.8 of light's separation, and 7.3 × 0.79 =
566
+ 5.8. Chroma does not drop with it (14.5 against the page's 13.7, same 280° hue).
567
+
568
+ - **`makePalette` generated the same invisible surface for every brand.** The kit's dark half is
569
+ written by hand and the generator runs its own ladder, and the generated ladder had the same
570
+ missing rung: dark `sunk` sat at `l 0.209`, ΔL* 1.0 under the `l 0.218` page it is supposed to sit
571
+ under, and it is now `l 0.168`.
572
+ A product generating a palette from its own brand colour inherited the invisible filter bar. The
573
+ rung now sits below the rail, which is also its order in light, and measures ΔL* 4.9–5.2 across
574
+ the ten brand colours the suite covers.
575
+
576
+ - **Two gates were silent on it, so both now measure it.** Surface separation was only ever asked
577
+ of `--color-edge` and `--color-line` (`check-token-contrast`) and of `navHoverBg · rail`
578
+ (`measurePalette`). Both now carry the sunk/page and sunk/shell pairs against the same 2.0 floor
579
+ that already stood for "a second ground on top of a ground must read as changed". Verified by
580
+ putting the old value back: the gate names ΔL* 1.1 and fails.
581
+
582
+ - **The danger icon button's glyph nearly disappeared in dark.** `.tamga-icon-btn-danger` fills with
583
+ `--color-critical-mark`, which is the same wine red in both themes, and painted its glyph with
584
+ `--color-page`, which is not: near-white in light (5.26) and near-black in dark (2.96). The design
585
+ writes this pair as fixed (`#fff` on `#9E2A3A`), and the ink is now `--color-inverse-ink`, white in
586
+ both themes, 5.89 on the plate either way. The pair is measured by `check-token-contrast` now.
587
+
588
+ - **A new gate measures the whole class: a theme-fixed ground under a theme-flipping ink.**
589
+ `check-tema-cifti` resolves every kit rule that sets both `background` and `color` down to hex in
590
+ both themes, and fails when exactly one side is theme-fixed and either theme falls under AA. This
591
+ same shape had already been fixed three times by hand this cycle (the selection bar, the marked
592
+ link at 1.36, and the danger icon button), each time without anything to stop the fourth. It
593
+ measures 12 mixed pairs today. What it deliberately does not see: a ground and an ink split across
594
+ two rules, which needs real CSS resolution rather than a per-rule read.
595
+
596
+ - **A comment claimed `--color-hover` was sunk's value; it is not, in either theme.** Light hover
597
+ is `#f7f5ef` and sunk `#e1ddd3`; dark hover is `#1a2842` and sunk `#020c21`. The two had drifted
598
+ apart long before, on both sides, and the comment was still asserting the old identity.
599
+
600
+ - **A tall `Dialog` cut off its own close button.** The panel had no bound: content taller than the
601
+ window made a `wide` dialog 1014px tall inside a 783px viewport, and since the shell centres its
602
+ child, 116px was clipped off the top and 116px off the bottom. The title and the close control
603
+ live in that top 116px. Escape still worked; the mouse had no way out. The body scroll that
604
+ fixes this had been written for `size="full"` only, and the other three sizes overflowed in
605
+ silence. The panel is now bounded and the body scrolls, with the header and footer held in
606
+ place.
607
+
608
+ - **Nine things the port had skipped.** Comparing `kit.css` class by class against the last commit
609
+ turned up 58 untouched classes; most are layout utilities and the mascot's furniture and were
610
+ right to leave alone, but nine were still speaking the old language:
611
+
612
+ - `Button size="sm"` carried the full-size 4px layer and 4px press, so the only thing "small"
613
+ about it was its width. It now steps down to 3px, like every other reduced control.
614
+ - A selected `RadioGroup` card or chip was marked with the accent colour and a 2px layer, while
615
+ every other selection in the kit — swatch, theme card, rail row, segment — is marked with the
616
+ edge and a lift. On a light brand the accent and the edge fall close enough together that the
617
+ selection disappeared.
618
+ - An invalid field sat at 2px while focus moved to 3px, so clicking into a field with an error
619
+ RAISED it.
620
+ - `Progress` had a 1px edge and a flat ink fill. It takes the 1.5px edge, a 3px layer, and a
621
+ striped accent fill with a hard right edge, so where the fill stops is exact at any brand.
622
+ - `Slider` was round — both the track and the thumb — against the kit's own written rule that
623
+ only the radio, the score ring and the ring spinner are circles. The thumb is now a 24px
624
+ square that can actually be grabbed.
625
+ - `RichText`'s toolbar buttons were flat, and an active one filled with plain ink, a colour the
626
+ kit uses nowhere else. They are boxed now, and an active one fills with the accent and sits on
627
+ its own base.
628
+ - `ImageField` was a thin 1px box; empty, it was a dashed rectangle with a normal surface
629
+ inside, so a filled field and an empty one read at the same weight. Empty is now hatched, the
630
+ same hatch the custom colour swatch uses.
631
+ - `LogoTile` took the 1.5px edge and the button radius.
632
+ - `Surface` drew its edge with `--color-edge`, the colour reserved for an object's own rule, so
633
+ the screen's ground read as heavily as the cards standing on it. It uses `--color-line`.
634
+
635
+ ### Added
636
+
637
+ - **A gate for the roundness rule** (`check:yuvarlak`). The rule was written down in two places
638
+ and enforced nowhere, which is why the slider stayed round for months without anyone noticing —
639
+ on its own it does not look wrong; what is wrong is that it disagrees with everything beside it.
640
+ The gate reads `kit.css` and allows `border-radius: 50%` only on a named list of selectors, so
641
+ adding a circle is now a decision someone has to write down.
642
+
643
+ ### Breaking
644
+
645
+ - **`LogView` takes a `labels` object.** New required prop: `labels={{ newLines: (n) => … }}`,
646
+ the text of the button described under Added. It is required rather than optional because a kit
647
+ that silently drops a behaviour when a string is missing is worse than one that asks for the
648
+ string.
649
+
650
+ - **Two names went back to the design's vocabulary.** The kit had invented both, and in the
651
+ design each name belongs to a different component:
652
+
653
+ `Rise` was a block sliding in from below; in the design it is **the number climbing from zero
654
+ to its target**. The animation is now **`Reveal`** (class `.tamga-rise` → `.tamga-reveal`), and
655
+ `Rise` is the change indicator. Our own docs page for it opened with "the name may be
656
+ misleading", which was the warning.
657
+
658
+ `SaysBubble` was the bubble over an empty state's drawing; in the design it is **the
659
+ conversation bubble** (a customer message, our reply, an assistant's suggestion). The drawing's
660
+ bubble is now **`ArtSays`**, which `EmptyState` uses internally, and `SaysBubble` is the chat
661
+ bubble. With both under one name, whoever looked for an empty state and whoever looked for a
662
+ chat found the wrong component.
663
+
664
+ - **`Delta` is a chip, not an inline percentage.** A trend glyph plus the value, filled with the
665
+ tone's own ink: beside a KPI the old outline version was too quiet to read. Good news takes the
666
+ **accent**, not green — green is a STATE colour ("resolved") while a rising revenue is a
667
+ movement — bad news stays red, and a flat change has no colour at all. `mono` is gone; the chip
668
+ sets its own numerals.
669
+
670
+ - **`ScheduleInput` is now a weekly schedule, not an interval `<select>`.** It used to take
671
+ `value` (minutes), `onChange` and an `options` list and render a single `<select>` — a native
672
+ select wearing a component name, which is the one thing a kit should not ship. It now takes
673
+ `days` (0 Sunday to 6 Saturday), `from`, `to`, their change handlers, a `labels` object and a
674
+ ready `summary` sentence, and it draws seven day buttons over an hour range.
675
+
676
+ Migration: for choosing an interval use `Select` with your own options — that is literally what
677
+ the old component was. For a weekly window, the new props are on the
678
+ [Schedule input](https://tamga.org.tr/docs/schedule-input) page; the summary line is the
679
+ caller's sentence, because "Every weekday" and "Her hafta içi" are not a translation the kit can
680
+ make.
681
+
682
+ - **`FileUpload.onReorder` takes a target index, not a direction.** `(id, -1 | 1)` became
683
+ `(id, target)`. The first signature was enough for the two arrow buttons; dragging broke it,
684
+ because moving the fourth image to the front is not a step. Two callbacks would have told one
685
+ story from two places, so there is one meaning left: *put this item at this index*.
686
+
687
+ Migration: the arrow buttons now send `i - 1` / `i + 1`, so a handler that swapped two entries
688
+ keeps working for them — but for dragging it must **splice**, not swap, or making the fourth
689
+ image the cover flings the cover into fourth place. The docs page shows the splice form.
690
+
691
+ - **`SettingsPanel` no longer takes `accent`.** The brand-coloured left edge existed for one
692
+ screen — the appearance screen — and that screen stopped stacking panels (see below), so the
693
+ prop had no caller left and its documented reason no longer described anything. Drop the prop;
694
+ nothing replaces it. A product that wants a coloured edge on a panel is describing a different
695
+ component, not a variant of this one.
696
+
697
+ - **`AppearanceLabels.brand` gained four required strings** — `customName`, `preview`,
698
+ `previewAction` and `lightNote` — because the brand row now names the chosen colour, prints its
699
+ hex, and shows a sample of it. The kit does not write text, so all four come from the caller.
700
+
701
+ ### Added
702
+
703
+ - **`Accordion.look`** — `cards` (the default) or `list`. The design draws both: sections as
704
+ their own raised cards, and the quiet stack inside one surface. The container decides, because
705
+ a prop on every section is the same decision taken again at every call site.
706
+
707
+ - **`ScrollX.controls` and `ScrollX.title`** — the two arrow buttons at the head of a strip, as
708
+ the design draws them. A click moves the strip by **one visible width** (90% of it, so the card
709
+ at the edge is not swallowed), never by "one card": that would need the strip to know what a
710
+ card is. The arrows do not replace the keyboard; the strip is focusable and scrolls with the
711
+ arrow keys already.
712
+
713
+ - **`Collapsible.icon`** — the glyph at the head of the row, in the accent: it says what kind of
714
+ section this is before the words are read.
715
+
716
+ - **`EmptyState` has a fourth layout, `plain`** — the dashed card the design draws: an icon tile
717
+ (`icon`, `tone`), a line and the first step, with no mascot. `art` is optional now. It is the
718
+ shape most screens need, and the dashed edge is the kit's own word for "not real content yet":
719
+ filled, the same place holds a solid card.
720
+
721
+ - **`Tag`** — the label badge: BETA, NEW, PRO. A sibling of `Badge` but not the same thing: a
722
+ counter carries a QUANTITY and disappears once read, a tag carries a STATE and stays. Three
723
+ looks — `dashed` for what is not real yet (a dashed edge says exactly that everywhere in the
724
+ kit), `solid` for what is new right now, `outline` for the quiet standing fact. The product
725
+ writes the word, the kit gives the shape.
726
+
727
+ - **`Spinner.look`** — `bars` (the default, the inline one that fits inside a control), `pixels`
728
+ (the mark's own 3×3 grid, pulsing along the diagonal, for a panel waiting on its first data)
729
+ and `ring` (the familiar circle, for a whole screen). The kit had only the bars, so every
730
+ screen-level wait borrowed a control-sized spinner.
731
+
732
+ - **`Descriptions.split`** — breaks the list into as many columns as fit (280px floor). At page
733
+ width a six-row list was leaving the right half empty; split, it reads as two short lists. Each
734
+ column keeps its own label column, so the values still line up.
735
+
736
+ - **`Code.filename`** — the name on the strip. Without it the strip still stands and holds the
737
+ copy button.
738
+
739
+ - **`InputGroup`** — the prefix/suffix box: a currency, a domain, a unit, a search glyph, a
740
+ shortcut key. The class for it had been in the kit with no component to draw it. A prefix is a
741
+ **piece**, not a string, so an `<Icon />` fits where "₺" fits, and the focus ring wraps the
742
+ whole box: one object, not two.
743
+
744
+ - **`Field.required`, `Field.note`, `Field.info` and `Field.count`.** The asterisk is a glyph
745
+ rather than a word (a word is a translation, and the kit makes none), `note` is the caller's
746
+ quiet word at the far end of the label row, `info` a mark that sits outside the `<label>` so
747
+ clicking it does not focus the control, and `count` the character counter under the control —
748
+ on the FIELD, because a textarea knows how many characters it holds, not how many it is
749
+ allowed.
750
+
751
+ - **`Button.variant="soft"`** — a step between primary and secondary, filled in the accent's
752
+ light tone. The action that says "you can also do this" without competing with the page's one
753
+ filled button.
754
+
755
+ - **`Link.look`** — `inline` (the default), `standalone` with an arrow that steps forward under
756
+ the pointer, `quiet` for a footer where twenty links cannot all be in the accent, and `marked`
757
+ for the one address in a paragraph that has to be found.
758
+
759
+ - **`Kbd.inline`** — the quieter key for a row in a shortcut list, where twenty layered boxes
760
+ would turn the list into a keypad.
761
+
762
+ - **`Card` can lead somewhere.** `href` (with `linkComponent`) or `onClick` makes the card a real
763
+ `<a>` or `<button>` and gives it the raised physics: it lifts under the pointer and presses
764
+ flat. A product card was being hand-rolled as a clickable `div`, which is invisible to the
765
+ keyboard and silent to a screen reader.
766
+
767
+ - **`SaysBubble`** — the conversation bubble, in three sides: `them` (left, quiet surface),
768
+ `me` (right, accent fill with a base) and `assistant` (a dashed, washed suggestion). The corner
769
+ facing the speaker is the small one; there is no tail, because a tail means a rotated square
770
+ and the kit has none. Time and delivery state live inside the bubble, where they do not break
771
+ the rhythm of a long thread.
772
+
773
+ - **`Kpi.look`** — `tile` (the default) is what a KPI grid is built from: a 40px icon box on the
774
+ left, the label over the number. `detail` is the taller card for a number that carries a curve
775
+ beside it and a change under it, the delta now drawn as a chip rather than an inline
776
+ percentage. The two are separate because of room: a card that fits a curve is taller than a
777
+ tile four of which stand side by side.
778
+
779
+ - **`Tabs.look="folder"` and `TabPanel`.** The design has two kinds of tab and the kit had one.
780
+ The line separates views of the page; the folder separates sections of one surface, sitting ON
781
+ its panel — the active tab's bottom edge is transparent and the tab drops by the panel's 1.5px
782
+ border, so the two read as one body.
783
+
784
+ - **`Segmented.size="sm"`** — the strip that lives in a toolbar. At the base size a segment
785
+ stands taller than the row it sits in; the docs example box's bar was 55px against the
786
+ reference's 45.
787
+
788
+ - **`yiginRenk` / `yiginRenkleri`** (from `tamga-ui/tone`) — the brand ramp a stacked chart's
789
+ slices step through, beside `seriRenk` for separate measures. Two names, so the two cases
790
+ cannot be confused.
791
+
792
+ - **`RailLink.badge`** (with `badgeLabel`) — how many things wait behind an entry. On a wide rail
793
+ it is the number; on a narrow one only a square, because a two-digit number sits on top of the
794
+ icon in a 40px box, and an unreadable number says no more than "there is something". The number
795
+ itself goes to the screen reader through `badgeLabel`, which is why that one is required.
796
+
797
+ - **`TabItem.count`** — the mono chip beside a tab's label: how many rows are in that tab. Mono,
798
+ because it is a number compared with the other tabs' numbers rather than a word to be read.
799
+
800
+ - **`Tooltip.bind`** — turns off the `aria-describedby` tie. On for everything except a trigger
801
+ whose accessible name is already the tooltip's text: a narrow rail item is labelled
802
+ "Orders" and its tip says "Orders", and a screen reader read it twice.
803
+
804
+ - **`Toast` has its own lifetime.** "It comes, and it goes" was the component's one sentence and
805
+ the going was left to the caller: every product wrote its own `setTimeout`, and the ones that
806
+ did not left notifications on screen. It is **5 seconds now, 8 with an `action`** ("Undo" has
807
+ to be readable *and* reachable before it leaves), `duration={0}` keeps it until dismissed, and
808
+ **the timer pauses while the pointer or keyboard focus is on it** — the hand reaching for
809
+ "Undo" travels straight across the toast.
810
+
811
+ `role` follows the tone: **`alert` only for `danger`**, because an assertive region cuts into
812
+ the sentence a screen reader is reading. Everything else stays `status`.
813
+
814
+ - **`MenuItem.shortcut`** — the keyboard shortcut drawn on the right of a menu row as a `Kbd`. It
815
+ does not bind the key: a menu that binds a global shortcut would fight the page that already
816
+ has one.
817
+
818
+ - **`LogView` says how many lines you missed.** Following only when you are already at the bottom
819
+ is the right behaviour, but it has a price: a line that arrives while you are reading further up
820
+ is out of sight, and without a word about it the stream looks stopped. A "↓ 12 new lines" button
821
+ now appears in that case and takes you down; the counter resets the moment you reach the bottom.
822
+
823
+ - **`LogLine.level`** — the level badge's word ("INFO", "WARN", "UYARI"). The kit draws the badge
824
+ and colours it from `tone`; the product writes the word, because a log level is not a string the
825
+ kit can translate.
826
+
827
+ - **`FileUpload` reorders by dragging** as well as with the arrow buttons. The dragged card fades
828
+ to 0.45 (lower reads as deleted) and the card it would land on takes the accent edge — the kit's
829
+ existing "this is the target" mark, not a new line invented for it. The arrows stay, because
830
+ drag-and-drop does not exist for someone on a keyboard.
831
+
832
+ - **`Checkbox.indeterminate`** — renders `aria-checked="mixed"` with a minus glyph. A parent row
833
+ whose children are partly selected had to be drawn either checked or unchecked, and both were
834
+ false; `TreeSelect` and any "select all" header need this third state.
835
+
836
+ - **`NumberInput.locale`** — which thousands separator and decimal mark to draw. It was hardcoded
837
+ to the browser's locale, so a Turkish panel viewed in an English browser showed `1,500.50` in a
838
+ form that submits `1.500,50`.
839
+
840
+ - **`RailCards`.** The sidebar width was a three-word `Segmented`: "Always narrow · Always wide ·
841
+ Let the user choose". On the same screen the theme was asked with pictures and this with words,
842
+ and how much room a menu takes is something you see rather than read. Three miniatures now, and
843
+ the third carries a collapse button at the foot of the rail because that is where it sits in the
844
+ panel.
845
+
846
+ - **`ThemeCards` takes a one-line note under each name** (`lightNote`, `darkNote`, `systemNote`),
847
+ and "System" is drawn as one panel cut on a diagonal rather than two rectangles side by side.
848
+ Two rectangles say "there are two themes"; the diagonal says "same panel, the device decides".
849
+
850
+ - **`PageBand` takes an `eyebrow`.** A short line above the title, for the question a title leaves
851
+ open — whether a setting applies to everyone or only to the person reading it.
852
+
853
+ ### Changed
854
+
855
+ - **The appearance screen is one card with dashed rows, not five stacked cards.** Five cards said
856
+ "a new subject starts here" five times, for five parts of one subject; and with the title above
857
+ the control in each, the eye zigzagged down the screen. Titles now sit in one column and
858
+ controls in another, split by the card's own width rather than the viewport, so the breakpoint
859
+ is right whether the rail is open or shut.
860
+
861
+ - **A colour swatch is the colour.** It was a white box holding a 20px square, with the check mark
862
+ deliberately left out because "its contrast changes with the chosen colour and disappears on a
863
+ light one". The reason was right and the remedy was not: the ink is now taken from the palette
864
+ that colour generates, so a light swatch gets dark ink and a dark one white. The square is 44px
865
+ and the check sits inside it.
866
+
867
+ The swatch no longer lifts on hover or sits on press. Its resting height is two different
868
+ numbers (0 unselected, 4 selected) and one hover ladder cannot serve both — tuned for the
869
+ unselected box, hover LOWERED the selected one. Law 1 · E and F caught it.
870
+
871
+ ## 0.4.4
872
+
873
+ ### Added
874
+
875
+ - **A fifth tone: `info`.** The scale answered one question — is something wrong — in four ways:
876
+ solved, heading there, broken, or not reporting. A product that only wants to SAY something had
877
+ to choose between a grey that reads as "not reporting" and a green that reads as "solved". Both
878
+ are a lie about the same sentence. `info` is blue because blue is the one hue this palette has
879
+ never attached urgency to, and like the other three it never decorates. It never pulses either:
880
+ "worth knowing" and "look here, now" are not the same sentence, and the pulse stays with the
881
+ second.
882
+
883
+ New tokens: `--color-info` and `--color-info-bg`, in both themes. The dark value leans toward
884
+ cyan rather than the brand's navy, for the same reason the other statuses stay warm there — a
885
+ navy signal sinks into a navy ground.
886
+
887
+ ### Changed
888
+
889
+ - **`Toast` takes its tone as the surface, not as a mark on its edge.** It was a white box with a
890
+ ten-pixel square in the corner. A toast is on screen for about two seconds, and what gets read in
891
+ that time is the colour of the box, not a square beside the text. The wash was already designed
892
+ for this — `--color-*-bg` is documented as "a tint, not a fill" — so the tone now paints the
893
+ surface and the square stays as the secondary cue. `neutral` is unchanged and stays uncoloured,
894
+ which is its whole job on this scale.
895
+
896
+ The edge and the drop stay neutral on purpose: painting them in the tone as well put a hard frame
897
+ around a soft wash, and the box ended up shouting in the exact way the wash exists to prevent.
898
+
899
+ **This changes how every existing toast looks without any change at the call site.** Nothing to
900
+ migrate; if a product wants the old white box, `tone="neutral"` is it.
901
+
902
+ ### Fixed
903
+
904
+ - **A toast no longer overflows a container narrower than itself.** Its width was fixed at 320px,
905
+ so a narrow parent got a toast hanging out of it, and a 360px phone got one hanging off the
906
+ screen (320 plus two 24px gutters does not fit). The measurement moved to `ToastViewport`, which
907
+ is the thing that knows how much room there is: the column is 20rem and shrinks by the gutters
908
+ when the screen is narrower.
909
+
910
+ - **The contrast gate now measures a status colour on its own wash.** It only ever checked
911
+ `critical` against the shell, so the pair that a chip, a row and a toast actually put on screen
912
+ was never measured — and a new tone could be added without anything looking. The five pairs are
913
+ gated now, including `silent` on `chart-fill`, which had already failed once and was recorded in
914
+ a comment rather than a gate.
915
+
8
916
  ## 0.4.3
9
917
 
10
918
  ### Fixed