@thespielplatz/tsp-tools-theme 0.2.0 → 0.3.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 (39) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/README.md +147 -32
  3. package/assets/css/theme.css +86 -6
  4. package/components/TspBanner.vue +97 -0
  5. package/components/TspBrandTile.vue +46 -0
  6. package/components/TspCodeBlock.vue +68 -0
  7. package/components/TspContainer.vue +10 -2
  8. package/components/TspCopyButton.vue +40 -0
  9. package/components/TspCopyField.vue +102 -0
  10. package/components/TspFooterRow.vue +56 -0
  11. package/components/TspLanguageToggle.vue +75 -0
  12. package/components/TspNavBadge.vue +36 -0
  13. package/components/TspNavGroup.vue +58 -0
  14. package/components/TspNavItem.vue +68 -12
  15. package/components/TspNavLabel.vue +56 -0
  16. package/components/TspNavSection.vue +33 -0
  17. package/components/TspNavSubItem.vue +35 -0
  18. package/components/TspPageTabs.vue +46 -0
  19. package/components/TspReleaseNotes.vue +143 -0
  20. package/components/TspSectionCard.vue +52 -0
  21. package/components/TspSidebar.vue +96 -3
  22. package/components/TspSidebarFooter.vue +332 -37
  23. package/components/TspSiteFooter.vue +47 -16
  24. package/components/TspSiteHeader.vue +18 -2
  25. package/components/TspThemeToggle.vue +24 -5
  26. package/components/TspToolOf.vue +42 -0
  27. package/components/TspTopBar.vue +69 -0
  28. package/components/TspUserCard.vue +127 -0
  29. package/components/TspVersionBadge.vue +49 -0
  30. package/components/TspWordmark.vue +36 -4
  31. package/composables/useTspCopy.ts +72 -0
  32. package/composables/useTspNavDrawer.ts +14 -0
  33. package/composables/useTspReveal.ts +18 -0
  34. package/docs/api.md +394 -0
  35. package/docs/design-system.md +297 -0
  36. package/nuxt.config.ts +11 -0
  37. package/package.json +24 -11
  38. package/page-meta.d.ts +11 -0
  39. package/plugins/01.tspColorMode.ts +12 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,114 @@
1
+ # Changelog
2
+
3
+ Generated from conventional commits by [changelogen](https://github.com/unjs/changelogen)
4
+ (`npm run bump-version`). Releases before `v0.3.0` were reconstructed from history — the
5
+ changelog ADR 005 asks for had never been generated.
6
+
7
+ Consumers pin a caret range and do not auto-update, so a release only reaches a service when
8
+ that service's own spec says so. This package has no users of its own — every entry below is a
9
+ change to eight apps at once, at the moment each of them chooses to take it.
10
+
11
+ ## v0.3.0 — 2026-09-22
12
+
13
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/v0.2.1...main)
14
+
15
+ ### 🚀 Enhancements
16
+
17
+ - Quiet shell, language toggle and shared footer row ([347776a](https://github.com/inf0matics/tsp-tools-theme/commit/347776a))
18
+ - TspBrandTile — the frame around a service's mark ([62d6f3a](https://github.com/inf0matics/tsp-tools-theme/commit/62d6f3a))
19
+ - Sidebar footer rows, source variants, and the wordmark fix ([ed4be66](https://github.com/inf0matics/tsp-tools-theme/commit/ed4be66))
20
+ - Nav groups and section headers, white nav at rest ([a15a97c](https://github.com/inf0matics/tsp-tools-theme/commit/a15a97c))
21
+ - Icons on the user row and on nav children ([97011c5](https://github.com/inf0matics/tsp-tools-theme/commit/97011c5))
22
+ - TspUserCard — the user page, rebuilt from stash ([4cd1a48](https://github.com/inf0matics/tsp-tools-theme/commit/4cd1a48))
23
+ - Move the language row below the divider ([62cde28](https://github.com/inf0matics/tsp-tools-theme/commit/62cde28))
24
+ - One meta row — theme, github, support, tip jar, version ([b1fe4fe](https://github.com/inf0matics/tsp-tools-theme/commit/b1fe4fe))
25
+ - Preferences row — theme label left, language toggle right ([4f0d4d8](https://github.com/inf0matics/tsp-tools-theme/commit/4f0d4d8))
26
+ - Nav badges, version tag icon, pre-addressed support mail ([c35e734](https://github.com/inf0matics/tsp-tools-theme/commit/c35e734))
27
+ - Truncated nav labels reveal their full text on hover ([17471ab](https://github.com/inf0matics/tsp-tools-theme/commit/17471ab))
28
+ - Page structure — logged-in vs logged-out, and external nav rows ([b777621](https://github.com/inf0matics/tsp-tools-theme/commit/b777621))
29
+ - The sidebar works on mobile — drawer, top bar, burger ([323ce31](https://github.com/inf0matics/tsp-tools-theme/commit/323ce31))
30
+ - Harvest the old mockups; quiet the sidebar scrollbar ([0442ba3](https://github.com/inf0matics/tsp-tools-theme/commit/0442ba3))
31
+ - Landing page reshaped from stash; forms page; drop the prototypes ([a4db2bb](https://github.com/inf0matics/tsp-tools-theme/commit/a4db2bb))
32
+ - MCP settings page, generalised from trips ([1046d24](https://github.com/inf0matics/tsp-tools-theme/commit/1046d24))
33
+ - MCP settings in the scoped demo; card and code sections on /content ([3085716](https://github.com/inf0matics/tsp-tools-theme/commit/3085716))
34
+ - Release surfaces — banner, notes overview, unread badge, modal ([4bbe783](https://github.com/inf0matics/tsp-tools-theme/commit/4bbe783))
35
+ - Widen the app measure to 1152px ([b0e189d](https://github.com/inf0matics/tsp-tools-theme/commit/b0e189d))
36
+
37
+ ### 🩹 Fixes
38
+
39
+ - Honour an explicit --port in scripts/dev.mjs ([fab8186](https://github.com/inf0matics/tsp-tools-theme/commit/fab8186))
40
+ - Centre the mark again; add checkboxes, radios and a textarea ([4da3df5](https://github.com/inf0matics/tsp-tools-theme/commit/4da3df5))
41
+ - Give the surfaces three rungs, so a card is not the sidebar ([a53ce8e](https://github.com/inf0matics/tsp-tools-theme/commit/a53ce8e))
42
+ - Pin the shell so it cannot scroll away; gate both apps for icons ([a44bb52](https://github.com/inf0matics/tsp-tools-theme/commit/a44bb52))
43
+ - **review:** Close the review findings — two guards did not actually guard ([1d621ed](https://github.com/inf0matics/tsp-tools-theme/commit/1d621ed))
44
+ - Reserve the scrollbar gutter so the content column stops shifting ([01de755](https://github.com/inf0matics/tsp-tools-theme/commit/01de755))
45
+ - TspUserCard no longer caps the page width ([cc11f04](https://github.com/inf0matics/tsp-tools-theme/commit/cc11f04))
46
+ - The MCP settings page no longer caps its own width ([65b4cbd](https://github.com/inf0matics/tsp-tools-theme/commit/65b4cbd))
47
+
48
+ ### 💅 Refactors
49
+
50
+ - Consolidate duplicated behaviour; docs move into the package ([b34e898](https://github.com/inf0matics/tsp-tools-theme/commit/b34e898))
51
+
52
+ ### 📖 Documentation
53
+
54
+ - Correct the @nuxt/icon pin notes for 2.5.1 ([43e68b7](https://github.com/inf0matics/tsp-tools-theme/commit/43e68b7))
55
+ - Reconcile README with the shipped theme; share TspToolOf ([4f72351](https://github.com/inf0matics/tsp-tools-theme/commit/4f72351))
56
+ - Generate the API reference, and document 71 undocumented props ([30a77db](https://github.com/inf0matics/tsp-tools-theme/commit/30a77db))
57
+
58
+ ### 🏡 Chore
59
+
60
+ - Check in the working agreement and dev launch config ([426dba2](https://github.com/inf0matics/tsp-tools-theme/commit/426dba2))
61
+ - Add scripts/dev.mjs so dev servers pick a free port ([996d0ef](https://github.com/inf0matics/tsp-tools-theme/commit/996d0ef))
62
+ - **release:** V0.3.0 ([0a5c793](https://github.com/inf0matics/tsp-tools-theme/commit/0a5c793))
63
+ - **playground:** Nav prototype — sub-items and section headers ([7c8938b](https://github.com/inf0matics/tsp-tools-theme/commit/7c8938b))
64
+
65
+ ### ✅ Tests
66
+
67
+ - E2e harness covering both consumption modes ([bff7d0a](https://github.com/inf0matics/tsp-tools-theme/commit/bff7d0a))
68
+
69
+ ## v0.2.1 — 2026-07-28
70
+
71
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/v0.2.0...v0.2.1)
72
+
73
+ ### 🩹 Fixes
74
+
75
+ - Cap @nuxt/icon at ~2.3.1 and gate on icon rendering ([9e4d67d](https://github.com/inf0matics/tsp-tools-theme/commit/9e4d67d))
76
+
77
+ ### 🏡 Chore
78
+
79
+ - **release:** V0.2.1 ([1d0925b](https://github.com/inf0matics/tsp-tools-theme/commit/1d0925b))
80
+
81
+ ## v0.2.0 — 2026-07-28
82
+
83
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/v0.1.3...v0.2.0)
84
+
85
+ ### 🚀 Enhancements
86
+
87
+ - Site shell, container widths, localisable theme toggle ([a16c5ff](https://github.com/inf0matics/tsp-tools-theme/commit/a16c5ff))
88
+
89
+ ### 🏡 Chore
90
+
91
+ - **release:** V0.2.0 ([0113c4c](https://github.com/inf0matics/tsp-tools-theme/commit/0113c4c))
92
+
93
+ ## v0.1.3 — 2026-07-28
94
+
95
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/v0.1.2...v0.1.3)
96
+
97
+ ### 🩹 Fixes
98
+
99
+ - Keep colour-mode assignment type-safe under color-mode v4 ([4eb970b](https://github.com/inf0matics/tsp-tools-theme/commit/4eb970b))
100
+
101
+ ### 🏡 Chore
102
+
103
+ - Stop the release script from double-bumping ([14fc551](https://github.com/inf0matics/tsp-tools-theme/commit/14fc551))
104
+ - **release:** V0.1.3 ([aa248ae](https://github.com/inf0matics/tsp-tools-theme/commit/aa248ae))
105
+
106
+ ## v0.1.2 — 2026-07-28
107
+
108
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/edd310cbdf8428b32bb3472164d99e0cbaf53d79...v0.1.2)
109
+
110
+ ### 🏡 Chore
111
+
112
+ - Prepare package for npm publishing ([cde3422](https://github.com/inf0matics/tsp-tools-theme/commit/cde3422))
113
+ - **release:** V0.1.1 — exports ./package.json + CSS-ownership docs ([9629dd0](https://github.com/inf0matics/tsp-tools-theme/commit/9629dd0))
114
+ - **release:** V0.1.2 ([f18e614](https://github.com/inf0matics/tsp-tools-theme/commit/f18e614))
package/README.md CHANGED
@@ -16,6 +16,16 @@ Two consumption modes:
16
16
 
17
17
  ---
18
18
 
19
+ > **The design system lives here:** [`docs/design-system.md`](docs/design-system.md) — tokens,
20
+ > components, rules and gates, read from `assets/css/theme.css` and `components/`. It is the
21
+ > source of truth for how tsp.tools services look; the planning project's
22
+ > `architecture/design-system.md` is a pointer to it.
23
+ >
24
+ > The **playground is the reference**, never a mockup. `npm run dev`.
25
+ >
26
+ > **Every prop, slot and emit:** [`docs/api.md`](docs/api.md) — generated from the components by
27
+ > `npm run docs:api` and verified in the gate, so it cannot go stale.
28
+
19
29
  ## Install
20
30
 
21
31
  ```bash
@@ -34,6 +44,30 @@ Peer expectations (provide them in the consuming app):
34
44
  > (A deep relative path such as `extends: ['../..']` can fail to pick up the source dirs;
35
45
  > the package name always works.)
36
46
 
47
+ ### Why `@nuxt/icon` is pinned at all
48
+
49
+ The layer declares `@nuxt/icon` as a **direct dependency** on a tilde range — currently
50
+ `~2.5.1`. The range is deliberate, even though the original regression is fixed.
51
+
52
+ `@nuxt/icon` 2.4.1 changed its plugin to take `$fetch` from `useRequestFetch()` and then read
53
+ `.native` off it. During SSR that returns Nitro's request-scoped `event.$fetch`, which has no
54
+ `.native` property — so Iconify's fetch was set to `undefined` and **every server-side icon load
55
+ failed**, logging `[Icon] failed to load icon …` per request. The layer capped at `~2.3.1` to
56
+ escape it. 2.5.1 no longer has the bug, so the cap was lifted (2026-09-20).
57
+
58
+ It stays a **tilde** range rather than a caret: `@nuxt/ui` only requires `^2.3.x`, so any
59
+ re-resolve of the tree can move consumers across minors on its own, and that is precisely how
60
+ the broken version reached apps the first time. A tilde means a future 2.6 has to be chosen,
61
+ and re-gated, by a human.
62
+
63
+ The pin is in `dependencies` rather than `overrides` on purpose: npm honours `overrides` only
64
+ in the root project, so an `overrides` entry here would not reach consuming apps.
65
+
66
+ **Any change to this version must be re-gated with `npm run test:icons`**, which boots the built
67
+ playground and fails on a single `failed to load icon` in the server log. Icons resolve at
68
+ runtime, per request — lint, typecheck and the build all stay green with every glyph missing,
69
+ which is exactly why this is easy to miss.
70
+
37
71
  ---
38
72
 
39
73
  ## Global usage (whole app themed)
@@ -57,16 +91,28 @@ shell components wherever you like:
57
91
  <template>
58
92
  <div class="flex min-h-svh bg-default text-default">
59
93
  <TspSidebar>
60
- <template #brand><TspWordmark>trips<span class="text-primary">.</span></TspWordmark></template>
94
+ <template #brand>
95
+ <TspBrandTile><img src="/logo.svg" alt="trips"></TspBrandTile>
96
+ <TspWordmark name="trips" />
97
+ </template>
61
98
  <template #nav>
62
- <TspNavItem to="/" icon="i-tabler-car">Fahrten</TspNavItem>
63
- <TspNavItem to="/settings" icon="i-tabler-settings">Einstellungen</TspNavItem>
99
+ <TspNavItem to="/" icon="i-tabler-car" badge="Owner" badge-tone="primary">Fahrten</TspNavItem>
100
+ <TspNavGroup to="/reports" icon="i-tabler-file-text" label="Berichte">
101
+ <TspNavSubItem to="/reports/tax" icon="i-tabler-receipt">Steuer</TspNavSubItem>
102
+ </TspNavGroup>
103
+ <TspNavSection label="Extern" />
104
+ <TspNavItem to="/handbook" icon="i-tabler-book">Handbuch</TspNavItem>
64
105
  </template>
65
106
  <template #footer>
66
- <TspSidebarFooter github-link="https://github.com/…" version="v1.2.0">
67
- <template #user>…your user link…</template>
68
- <template #logout>…your logout…</template>
69
- </TspSidebarFooter>
107
+ <TspSidebarFooter
108
+ v-model="locale"
109
+ app-name="trips"
110
+ user="fil" user-to="/user"
111
+ settings-to="/settings" logout-href="/auth/logout"
112
+ :locales="['de', 'en']"
113
+ github-link="https://github.com/…" version="v1.2.0" version-link="https://github.com/…/releases/tag/v1.2.0"
114
+ tip-jar tool-of
115
+ />
70
116
  </template>
71
117
  </TspSidebar>
72
118
  <main class="flex-1 min-w-0 bg-default">
@@ -125,20 +171,26 @@ scope:
125
171
  |---|---|---|
126
172
  | `primary` (amber) | `#fbad18` | `#fbad18` |
127
173
  | `error` (tomato) | `#ff6347` | `#ff6347` |
128
- | `bg` | `#f8f9fa` | `#212529` |
129
- | `surface` (`bg-elevated`) | `#ffffff` | `#2b3035` |
174
+ | `bg` (page) | `#f1f3f5` | `#212529` |
175
+ | `bg-muted` (the shell) | `#f8f9fa` | `#2b3035` |
176
+ | `bg-elevated` (raised content) | `#ffffff` | `#31373e` |
130
177
  | `border` | `#dee2e6` | `#404041` |
131
178
  | `text` | `#212529` | `#f0f0f0` |
132
179
  | `on-primary` (`text-inverted`) | `#212529` | `#212529` |
133
180
 
134
- Brand colours (amber, tomato) are the same in both modes; only bg/surface/border/text flip.
181
+ Brand colours (amber, tomato) are the same in both modes; only surfaces, borders and text flip.
182
+ **Three rungs, not two** — the shell uses `bg-muted`, raised content uses `bg-elevated`. See
183
+ [`docs/design-system.md`](docs/design-system.md) for the full ladder and the reason it changed.
135
184
  The amber ramp is also available as Tailwind utilities (`*-tsp-amber-{50..950}`).
136
185
 
137
186
  **Fonts** — Nunito (text + headings) and Space Grotesk (logo), via `@nuxt/fonts`. Use the
138
187
  `tsp-wordmark` class (or `<TspWordmark>`) for Space Grotesk; everything else is Nunito.
139
188
 
140
- **Composable** — `useTspColorMode()` → `{ pref, toggle }`, the remembered (cookie) preference
141
- for the themed area.
189
+ **Composables** (auto-imported) — `useTspColorMode()` → `{ pref, toggle }`, the remembered
190
+ (cookie) preference for the themed area; `useTspNavDrawer()` → `{ isOpen, open, close, toggle }`,
191
+ the mobile drawer's shared state; `useTspCopy()` → `{ copied, copy }`, clipboard plus a moment of
192
+ confirmation, with a fallback for non-secure contexts; `useTspReveal()` → `{ revealed, toggle,
193
+ mask }`, show/hide a secret with a fixed-length placeholder.
142
194
 
143
195
  **Components** (auto-imported):
144
196
 
@@ -146,16 +198,42 @@ for the themed area.
146
198
  |---|---|
147
199
  | `TspThemeProvider` | Scoped wrapper — marks a sub-area as themed (`.tsp-theme`). |
148
200
  | `TspSidebar` | App-shell sidebar frame; slots `#brand` `#nav` `#footer`. |
149
- | `TspSidebarFooter` | User → Logout → divider → theme toggleGitHub + version. |
201
+ | `TspSidebarFooter` | User → Settings → Logout → divider → theme + language meta icons + version → "A tool of tsp.tools". Rows are **props**, not slots. |
150
202
  | `TspSiteHeader` | Public/marketing header; slots `#brand` `#nav` `#actions`, prop `sticky`. |
151
203
  | `TspSiteFooter` | Public/marketing footer; slots `#brand` `#links` `#actions`. |
152
204
  | `TspThemeToggle` | Sun/moon light–dark toggle (uses `useTspColorMode`). |
153
- | `TspNavItem` | Sidebar nav link (muted filled-amber when active). |
154
- | `TspWordmark` | Space Grotesk + amber brand wordmark. |
205
+ | `TspNavItem` | Sidebar nav link **white at rest, amber text when active**. Optional `badge`. |
206
+ | `TspNavGroup` | A nav item with always-visible sub-items on a hairline spine. |
207
+ | `TspNavSubItem` | A child row — muted, own icon, amber when active. |
208
+ | `TspNavSection` | A rule, optionally labelled (`External`). |
209
+ | `TspNavBadge` | Outline pill on a nav row; tones `primary` \| `neutral`. |
210
+ | `TspNavLabel` | Truncates a label and reveals it in full on hover. Used by the rows above. |
211
+ | `TspBrandTile` | The frame around a service's mark — `sidebar` (24px) or `hero` (64/96px). |
212
+ | `TspUserCard` | Account card for a service's user page; masked id + manage link. |
213
+ | `TspFooterRow` | One sidebar-footer row — `to` / `href` / `button`. |
214
+ | `TspLanguageToggle` | DE/EN segmented control; all strings are props. |
215
+ | `TspVersionBadge` | Version pill with a tag icon; a link only when given `href`. |
216
+ | `TspWordmark` | `<TspWordmark name="trips" />` → white name + amber dot. |
217
+ | `TspTopBar` | Mobile top bar — burger, brand, page title. Pairs with `TspSidebar`. |
218
+ | `TspSectionCard` | Icon + title over content, on the raised surface. |
219
+ | `TspPageTabs` | Area tabs as routes, so the back button works. |
220
+ | `TspCopyField` | Read-only value + copy; `secret` masks it behind a reveal. |
221
+ | `TspCopyButton` | Copy with a tick confirmation. |
222
+ | `TspCodeBlock` | Config to copy; shown and copied values can differ. |
223
+ | `TspBanner` | Dismissible strip; emits `dismiss`, remembering it is the app's job. |
224
+ | `TspReleaseNotes` | Release notes; props mirror the `tsp-release` skill's shape. |
225
+ | `TspToolOf` | "A tool of tsp.tools". Shared by both footers. |
155
226
  | `TspContainer` | Centered content column; props `width` (`app` \| `site`) and `padded`. |
156
227
 
157
- App-specific content (branding, nav lists, user identity, GitHub/version values) is injected
158
- via slots/props — none of it lives in the layer.
228
+ App-specific content (branding, nav lists, user identity, GitHub/version values) is injected via
229
+ props — none of it lives in the layer. **Prefer props over slots** for the footer rows every app
230
+ has: a slot invites each app to restyle its own copy, which is the drift `TspFooterRow` exists to
231
+ stop. `#user` survives only for apps needing more than a name.
232
+
233
+ **Open source is derived from `github-link`.** Set it and the meta row shows a GitHub icon and the
234
+ version badge links to the release; leave it out and the badge is plain text — **`version-link`
235
+ is then ignored**, because a closed-source project has no release page to point at. There is no
236
+ `variant` prop; a second source of truth could disagree with the first.
159
237
 
160
238
  ### App shell vs site shell
161
239
 
@@ -164,20 +242,21 @@ use `TspSiteHeader` + `TspSiteFooter` with the wide container:
164
242
 
165
243
  ```vue
166
244
  <TspSiteHeader>
167
- <template #brand><NuxtLink to="/"><TspWordmark>tsp<span class="text-primary">.</span>tools</TspWordmark></NuxtLink></template>
245
+ <template #brand><NuxtLink to="/"><TspWordmark name="trips" /></NuxtLink></template>
168
246
  <template #nav><a href="#tools">Tools</a></template>
169
- <template #actions><TspThemeToggle /></template>
247
+ <template #actions><TspThemeToggle icon-only /></template>
170
248
  </TspSiteHeader>
171
249
 
172
250
  <TspContainer width="site"><NuxtPage /></TspContainer>
173
251
 
174
252
  <TspSiteFooter>
175
- <template #brand><TspWordmark>tsp<span class="text-primary">.</span>tools</TspWordmark></template>
253
+ <template #brand><TspWordmark name="trips" /></template>
176
254
  <template #links><NuxtLink to="/imprint">Imprint</NuxtLink></template>
177
255
  </TspSiteFooter>
178
256
  ```
179
257
 
180
- `TspContainer` defaults to `width="app"` (`max-w-4xl`, `px-6 sm:px-10`) — the app/admin measure.
258
+ `TspContainer` defaults to `width="app"` (`max-w-6xl` = 1152px, `px-6 sm:px-10`) — the app/admin
259
+ measure. Cap prose on the paragraph (`max-w-2xl`), not by narrowing the page.
181
260
  `width="site"` is `max-w-[1240px]`, `px-6 md:px-10`, the marketing measure. Both add `pt-10 pb-16`
182
261
  unless you pass `:padded="false"` (which is what the header/footer frames do internally).
183
262
 
@@ -193,8 +272,10 @@ button shows and announces while dark:
193
272
  />
194
273
  ```
195
274
 
196
- The defaults are English. The default slot receives `{ isDark, label }` if you want different
197
- markup (an icon-only square button, say) while keeping the mechanism.
275
+ The visible defaults are **`Light` / `Dark`** one word, because "mode" earns nothing beside a
276
+ sun icon and the label shares a 224px row with the language control. The aria-labels keep the
277
+ full "Switch to light mode". Pass `icon-only` for a compact icon button (the site header and
278
+ tight rows); the default slot still receives `{ isDark, label }` if you want other markup.
198
279
 
199
280
  ---
200
281
 
@@ -246,18 +327,52 @@ They are excluded from the published package (`files` allowlist).
246
327
 
247
328
  ## Release
248
329
 
249
- The version bump and the git tag are **not** done by an npm script:
330
+ One command (ADR 005):
250
331
 
251
- 1. Bump `package.json` `.version`, commit, and push to `main`.
252
- 2. Publish a GitHub Release for that commit — GitHub creates the `vX.Y.Z` tag on publish.
253
- 3. Ship the package:
332
+ ```bash
333
+ npm run bump-version
334
+ ```
335
+
336
+ It runs the full gate, then `changelogen --release` writes `CHANGELOG.md`, bumps
337
+ `package.json`, commits and tags `vX.Y.Z`, then pushes the commit and the tag.
338
+
339
+ **The pushed tag is what publishes.** `.github/workflows/release.yml` re-runs the gate on a
340
+ clean checkout of the tagged commit and publishes to npm with provenance, so the tarball eight
341
+ services install is built from a tagged commit rather than from whatever a laptop had in its
342
+ working tree. It also refuses to publish if the tag and `package.json` disagree.
254
343
 
255
- ```bash
256
- npm run release # lint, then npm publish
257
- ```
344
+ | Command | |
345
+ |---|---|
346
+ | `npm run gate` | everything CI runs — lint, `docs:api:check`, typecheck, icons, E2E |
347
+ | `npm run bump-version` | the release: gate → changelog + version + tag → push |
348
+ | `npm run release` | break-glass — gate then `npm publish` from here, for when Actions is down |
349
+
350
+ **There is no npm token.** The package uses npm **trusted publishing**: npmjs.com is configured
351
+ to trust this repository and this workflow file, GitHub mints a short-lived OIDC token for the
352
+ run, and npm verifies it. Nothing long-lived is stored in the repo, so there is no secret to
353
+ rotate and none to leak. Two consequences worth knowing:
354
+
355
+ - `id-token: write` in `release.yml` **is** the authentication. Remove it and the publish has no
356
+ way to authenticate at all.
357
+ - The workflow upgrades npm before publishing. Node 22 bundles npm 10, which has no OIDC support
358
+ and would silently look for a token that does not exist; trusted publishing needs npm ≥ 11.5.1
359
+ and Node ≥ 22.14.0, and the step fails loudly rather than falling back.
360
+
361
+ Provenance is automatic under trusted publishing — `--provenance` is not passed, and must not be.
362
+
363
+ **`0.3.0` is a special case: it needs a tag, not a bump.** Its version was already written into
364
+ `package.json` by hand (`0a5c793`) and its changelog entry is written, so two of changelogen's
365
+ three jobs are done. The third — `changelogen --release` — cannot run: it refuses a bump whose
366
+ result equals the current version (`bumpVersion` returns `false`, reported as "Unable to bump
367
+ version based on changes"), and `-r 0.3.0` on a package already at `0.3.0` is exactly that. So
368
+ tag it directly:
369
+
370
+ ```bash
371
+ npm run gate && git tag -a v0.3.0 -m "v0.3.0" && git push --follow-tags
372
+ ```
258
373
 
259
- `npm run release` deliberately only lints and publishes. It never bumps the version or tags,
260
- so it can't drift from the version that was already committed and tagged in steps 1–2.
374
+ From `0.3.1` on, `npm run bump-version` is the path changelogen picks the next version from the
375
+ commits, so there is nothing to force.
261
376
 
262
377
  ## License
263
378
 
@@ -89,15 +89,39 @@
89
89
  modes (the `on-primary` token). */
90
90
  --ui-text-inverted: #212529;
91
91
 
92
- --ui-radius: 0.5rem;
92
+ /* Brand amber used as TEXT on the current surface (not as a fill). Amber-400
93
+ is the brand primary and is correct on the dark surfaces; on light it fails
94
+ text contrast, so the light group steps down to amber-600 — see
95
+ design-system.md, accessibility note. Fills keep `--ui-primary`. */
96
+ --tsp-text-brand: var(--color-tsp-amber-400);
97
+
98
+ /* Radius base. Nuxt UI derives its whole scale from this:
99
+ rounded-sm = 1x = 4px · rounded-md = 1.5x = 6px
100
+ rounded-lg = 2x = 8px · rounded-xl = 3x = 12px
101
+ 0.25rem is what makes `rounded-xl` the 12px card radius that
102
+ design-system.md and ADR 018 specify, and `rounded-md` the 6px shell-row
103
+ radius. The previous 0.5rem is why cards rendered 24px (ADR 018 read that
104
+ as gage drift; it was this token). */
105
+ --ui-radius: 0.25rem;
93
106
  }
94
107
 
95
- /* Dark surfaces (default) — tsp anthracite. */
108
+ /* Dark surfaces (default) — tsp anthracite.
109
+
110
+ THREE RUNGS, NOT TWO. `muted` and `elevated` used to be the same #2b3035,
111
+ which left no surface for a raised card: it either matched the page or
112
+ matched the sidebar, and it matched the sidebar in stash, gage and trips.
113
+ The ladder is now
114
+ bg page
115
+ muted the shell — sidebar, top bar
116
+ elevated raised CONTENT — cards, the user card, feature boxes
117
+ accented pressed/selected chrome
118
+ and the shell sits below content rather than beside it, which is also what
119
+ ADR 018 wanted from a quiet shell. */
96
120
  .dark .tsp-theme,
97
121
  .dark.tsp-theme {
98
122
  --ui-bg: #212529;
99
123
  --ui-bg-muted: #2b3035;
100
- --ui-bg-elevated: #2b3035;
124
+ --ui-bg-elevated: #31373e;
101
125
  --ui-bg-accented: #404041;
102
126
  --ui-bg-inverted: #f0f0f0;
103
127
 
@@ -113,11 +137,16 @@
113
137
  --ui-text-highlighted: #ffffff;
114
138
  }
115
139
 
116
- /* Light surfaces — tsp light theme (design-system.md → Light theme). */
140
+ /* Light surfaces — tsp light theme (design-system.md → Light theme).
141
+
142
+ Same three rungs. Light had the same collapse (muted and elevated both
143
+ #ffffff) and nowhere left to go, because the page was already near-white:
144
+ the page steps down to #f1f3f5 so the shell can take #f8f9fa and a raised
145
+ card can be pure white. Raised means lighter here, the inverse of dark. */
117
146
  .light .tsp-theme,
118
147
  .light.tsp-theme {
119
- --ui-bg: #f8f9fa;
120
- --ui-bg-muted: #ffffff;
148
+ --ui-bg: #f1f3f5;
149
+ --ui-bg-muted: #f8f9fa;
121
150
  --ui-bg-elevated: #ffffff;
122
151
  --ui-bg-accented: #dee2e6;
123
152
  --ui-bg-inverted: #212529;
@@ -132,6 +161,57 @@
132
161
  --ui-text-toned: #343a40;
133
162
  --ui-text: #212529;
134
163
  --ui-text-highlighted: #16191c;
164
+
165
+ /* #fbad18 fails contrast as text on these surfaces; #f47920 passes. */
166
+ --tsp-text-brand: var(--color-tsp-amber-600);
167
+ }
168
+
169
+ /* Mode-correct brand text (ADR 018: the active nav item is amber TEXT, not an
170
+ amber fill). Written unlayered and with two classes so it beats Tailwind's
171
+ `text-muted` / `hover:text-*` utilities without needing `!important`. */
172
+ .tsp-theme .tsp-text-brand {
173
+ color: var(--tsp-text-brand);
174
+ }
175
+
176
+ /* Mode-correct brand colour as a BORDER, for the same reason .tsp-text-brand
177
+ exists for text: #fbad18 is the brand, but the light group steps down to
178
+ amber-600 so an accent edge holds up on light surfaces too. */
179
+ .tsp-theme .tsp-border-brand {
180
+ border-left-color: var(--tsp-text-brand);
181
+ }
182
+
183
+ /* Reserve the scrollbar's width whether or not the page scrolls.
184
+
185
+ The content column is centred, so it moves by HALF the scrollbar width the
186
+ moment a page gets tall enough to overflow: walking from a short page to a
187
+ long one shifted every heading, card and table sideways. Measured in Chrome
188
+ at 1400px, the playground put the column at 364px on a short route and at
189
+ 356.5px on a long one.
190
+
191
+ Both selectors are the same rule in the two consumption modes: GLOBAL puts
192
+ the scope class on <html> itself, SCOPED puts it on a provider inside the
193
+ page. Neither matches a consumer's own public pages, which this layer does
194
+ not own and must not relayout.
195
+
196
+ Browsers with overlay scrollbars (macOS default) are unaffected — there is no
197
+ width to reserve, so nothing changes for them either way. */
198
+ html.tsp-theme,
199
+ html:has(.tsp-theme) {
200
+ scrollbar-gutter: stable;
201
+ }
202
+
203
+ /* Scrollable, but without a visible scrollbar track. The sidebar nav has to
204
+ scroll when a service has a long nav or a short window, but a permanent
205
+ track beside 32px rows in a 224px column reads as clutter — and on platforms
206
+ with overlay scrollbars it appears and disappears as you move the mouse,
207
+ which is worse. Scrolling itself is untouched: wheel, trackpad, touch and
208
+ keyboard all still work. */
209
+ .tsp-scroll-quiet {
210
+ scrollbar-width: none;
211
+ }
212
+
213
+ .tsp-scroll-quiet::-webkit-scrollbar {
214
+ display: none;
135
215
  }
136
216
 
137
217
  /* The wordmark uses Space Grotesk; everything else stays Nunito. */
@@ -0,0 +1,97 @@
1
+ <!--
2
+ A dismissible strip across the top of the content area. Its first job is the
3
+ release announcement the tsp-release skill's `showBanner` flag turns on, but
4
+ nothing here is release-specific: it is a banner.
5
+
6
+ <TspBanner icon="i-tabler-sparkles" title="Version 1.3.0">
7
+ Neue Auswertung im Bericht.
8
+ <template #action><UButton …>Was ist neu?</UButton></template>
9
+ </TspBanner>
10
+
11
+ Amber TEXT and an amber left edge, not an amber fill: ADR 018 gives the fill
12
+ to primary buttons alone, and a full-width amber bar would be the loudest
13
+ thing on the page by a distance.
14
+
15
+ Dismissal is the CALLER's to remember. The banner emits `dismiss`; whether
16
+ that is per release, per user or per session is a question the layer cannot
17
+ answer, and guessing it wrong means a banner that never comes back or never
18
+ goes away.
19
+ -->
20
+ <template>
21
+ <div
22
+ v-if="!dismissed"
23
+ data-testid="tsp-banner"
24
+ role="status"
25
+ class="flex items-start gap-3 rounded-xl border border-default border-l-2 bg-elevated px-4 py-3"
26
+ :class="tone === 'brand' ? 'tsp-border-brand' : ''"
27
+ >
28
+ <UIcon
29
+ v-if="icon"
30
+ :name="icon"
31
+ class="mt-0.5 shrink-0 text-lg"
32
+ :class="tone === 'brand' ? 'tsp-text-brand' : 'text-muted'"
33
+ />
34
+
35
+ <div class="min-w-0 flex-1">
36
+ <p
37
+ v-if="title"
38
+ data-testid="tsp-banner-title"
39
+ class="text-sm font-bold text-highlighted"
40
+ >
41
+ {{ title }}
42
+ </p>
43
+ <div class="text-sm text-muted">
44
+ <slot />
45
+ </div>
46
+ </div>
47
+
48
+ <div class="flex shrink-0 items-center gap-2">
49
+ <slot name="action" />
50
+ <button
51
+ v-if="dismissible"
52
+ type="button"
53
+ data-testid="tsp-banner-dismiss"
54
+ :aria-label="dismissLabel"
55
+ :title="dismissLabel"
56
+ class="flex size-8 items-center justify-center rounded-md text-muted hover:text-highlighted hover:bg-default"
57
+ @click="dismiss"
58
+ >
59
+ <UIcon
60
+ name="i-tabler-x"
61
+ class="text-lg"
62
+ />
63
+ </button>
64
+ </div>
65
+ </div>
66
+ </template>
67
+
68
+ <script setup lang="ts">
69
+ withDefaults(defineProps<{
70
+ /** Headline. Without it the banner is only its default slot. */
71
+ title?: string
72
+ /** Leading icon, `i-tabler-*`. */
73
+ icon?: string
74
+ /** `brand` = amber accent edge, `neutral` = quiet. Neither is a fill. */
75
+ tone?: 'brand' | 'neutral'
76
+ /** Show the close button. The app decides whether it STAYS closed. */
77
+ dismissible?: boolean
78
+ /** Accessible name for the close button. */
79
+ dismissLabel?: string
80
+ }>(), {
81
+ title: undefined,
82
+ icon: undefined,
83
+ tone: 'brand',
84
+ dismissible: true,
85
+ dismissLabel: 'Dismiss',
86
+ })
87
+
88
+ const emit = defineEmits<{ dismiss: [] }>()
89
+
90
+ // Local only, so the banner disappears immediately on click. Whether it STAYS
91
+ // gone is the app's decision, via the event.
92
+ const dismissed = ref(false)
93
+ const dismiss = () => {
94
+ dismissed.value = true
95
+ emit('dismiss')
96
+ }
97
+ </script>
@@ -0,0 +1,46 @@
1
+ <!--
2
+ The frame a service's mark sits in — size, radius and clipping. The artwork
3
+ goes in the default slot; the layer ships no logo of its own and must not grow
4
+ one (spec 02).
5
+
6
+ <TspBrandTile><img src="/logo.svg" alt="gage"></TspBrandTile>
7
+ <TspBrandTile size="hero"><img src="/logo.svg" alt="gage"></TspBrandTile>
8
+
9
+ Two sizes, deliberately: `sidebar` (24px, for TspSidebar's #brand slot) and
10
+ `hero` (64px, 96px from sm up, for a landing lockup). Eight services were each
11
+ writing `size-6 shrink-0 rounded-[22%]` by hand.
12
+
13
+ THE FILL BELONGS TO THE ARTWORK, not to this component. Services ship a
14
+ logo.svg already drawn as amber tile plus dark mark, so the tile paints no
15
+ background — it sizes, clips and rounds. A service that passes a bare mark and
16
+ gets no amber is looking at its own bug, not a missing prop. (It also matters
17
+ in the shell: ADR 018 gives the amber fill to primary buttons alone.)
18
+
19
+ The radius is a PERCENTAGE so the corner scales with the size and both tiles
20
+ read as one shape. There is no `border` prop — the design system dropped the
21
+ old border-4, and a prop would keep it alive in the one place that outlives
22
+ the decision.
23
+
24
+ No animation here. An animated mark is the service's own component and goes in
25
+ the slot like any other content; the tile does not know whether what it holds
26
+ moves, and clips it either way.
27
+ -->
28
+ <template>
29
+ <div
30
+ data-testid="tsp-brand-tile"
31
+ :data-size="size"
32
+ class="shrink-0 overflow-hidden rounded-[22%]"
33
+ :class="size === 'hero' ? 'size-16 sm:size-24' : 'size-6'"
34
+ >
35
+ <slot />
36
+ </div>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ withDefaults(defineProps<{
41
+ /** `sidebar` = 24px (TspSidebar #brand), `hero` = 64px / 96px from sm up. */
42
+ size?: 'sidebar' | 'hero'
43
+ }>(), {
44
+ size: 'sidebar',
45
+ })
46
+ </script>