@thespielplatz/tsp-tools-theme 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/README.md +148 -44
  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 -13
  38. package/page-meta.d.ts +11 -0
  39. package/plugins/01.tspColorMode.ts +12 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,144 @@
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.1
12
+
13
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/v0.3.0...v0.3.1)
14
+
15
+ `0.3.1` exists to prove the automated release path end to end: `npm run bump-version` cuts it,
16
+ a pushed tag publishes it over OIDC with no npm token anywhere. It contains no behaviour change —
17
+ no consumer needs it.
18
+
19
+ Provenance was attempted and abandoned: npm signs provenance only for a **public** source
20
+ repository, and this one is private (`422 … Unsupported GitHub Actions source repository
21
+ visibility: "private"`).
22
+
23
+ ### 🩹 Fixes
24
+
25
+ - **ci:** Give the provenance check 10 minutes, not 200 seconds ([801182d](https://github.com/inf0matics/tsp-tools-theme/commit/801182d))
26
+ - **ci:** Drop provenance — it requires a public repository
27
+ - **ci:** Keep package-lock.json in step with the version on release
28
+
29
+ ### 🏡 Chore
30
+
31
+ - Changelogen must not cut the GitHub release ([c564e71](https://github.com/inf0matics/tsp-tools-theme/commit/c564e71))
32
+
33
+ ### 🤖 CI
34
+
35
+ - Ask for provenance explicitly, and verify it landed ([e96945a](https://github.com/inf0matics/tsp-tools-theme/commit/e96945a))
36
+
37
+ ### ❤️ Contributors
38
+
39
+ - Inf0matics <philipp.horwath@gmail.com>
40
+
41
+ ## v0.3.0 — 2026-09-22
42
+
43
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/v0.2.1...main)
44
+
45
+ ### 🚀 Enhancements
46
+
47
+ - Quiet shell, language toggle and shared footer row ([347776a](https://github.com/inf0matics/tsp-tools-theme/commit/347776a))
48
+ - TspBrandTile — the frame around a service's mark ([62d6f3a](https://github.com/inf0matics/tsp-tools-theme/commit/62d6f3a))
49
+ - Sidebar footer rows, source variants, and the wordmark fix ([ed4be66](https://github.com/inf0matics/tsp-tools-theme/commit/ed4be66))
50
+ - Nav groups and section headers, white nav at rest ([a15a97c](https://github.com/inf0matics/tsp-tools-theme/commit/a15a97c))
51
+ - Icons on the user row and on nav children ([97011c5](https://github.com/inf0matics/tsp-tools-theme/commit/97011c5))
52
+ - TspUserCard — the user page, rebuilt from stash ([4cd1a48](https://github.com/inf0matics/tsp-tools-theme/commit/4cd1a48))
53
+ - Move the language row below the divider ([62cde28](https://github.com/inf0matics/tsp-tools-theme/commit/62cde28))
54
+ - One meta row — theme, github, support, tip jar, version ([b1fe4fe](https://github.com/inf0matics/tsp-tools-theme/commit/b1fe4fe))
55
+ - Preferences row — theme label left, language toggle right ([4f0d4d8](https://github.com/inf0matics/tsp-tools-theme/commit/4f0d4d8))
56
+ - Nav badges, version tag icon, pre-addressed support mail ([c35e734](https://github.com/inf0matics/tsp-tools-theme/commit/c35e734))
57
+ - Truncated nav labels reveal their full text on hover ([17471ab](https://github.com/inf0matics/tsp-tools-theme/commit/17471ab))
58
+ - Page structure — logged-in vs logged-out, and external nav rows ([b777621](https://github.com/inf0matics/tsp-tools-theme/commit/b777621))
59
+ - The sidebar works on mobile — drawer, top bar, burger ([323ce31](https://github.com/inf0matics/tsp-tools-theme/commit/323ce31))
60
+ - Harvest the old mockups; quiet the sidebar scrollbar ([0442ba3](https://github.com/inf0matics/tsp-tools-theme/commit/0442ba3))
61
+ - Landing page reshaped from stash; forms page; drop the prototypes ([a4db2bb](https://github.com/inf0matics/tsp-tools-theme/commit/a4db2bb))
62
+ - MCP settings page, generalised from trips ([1046d24](https://github.com/inf0matics/tsp-tools-theme/commit/1046d24))
63
+ - MCP settings in the scoped demo; card and code sections on /content ([3085716](https://github.com/inf0matics/tsp-tools-theme/commit/3085716))
64
+ - Release surfaces — banner, notes overview, unread badge, modal ([4bbe783](https://github.com/inf0matics/tsp-tools-theme/commit/4bbe783))
65
+ - Widen the app measure to 1152px ([b0e189d](https://github.com/inf0matics/tsp-tools-theme/commit/b0e189d))
66
+
67
+ ### 🩹 Fixes
68
+
69
+ - Honour an explicit --port in scripts/dev.mjs ([fab8186](https://github.com/inf0matics/tsp-tools-theme/commit/fab8186))
70
+ - Centre the mark again; add checkboxes, radios and a textarea ([4da3df5](https://github.com/inf0matics/tsp-tools-theme/commit/4da3df5))
71
+ - Give the surfaces three rungs, so a card is not the sidebar ([a53ce8e](https://github.com/inf0matics/tsp-tools-theme/commit/a53ce8e))
72
+ - Pin the shell so it cannot scroll away; gate both apps for icons ([a44bb52](https://github.com/inf0matics/tsp-tools-theme/commit/a44bb52))
73
+ - **review:** Close the review findings — two guards did not actually guard ([1d621ed](https://github.com/inf0matics/tsp-tools-theme/commit/1d621ed))
74
+ - Reserve the scrollbar gutter so the content column stops shifting ([01de755](https://github.com/inf0matics/tsp-tools-theme/commit/01de755))
75
+ - TspUserCard no longer caps the page width ([cc11f04](https://github.com/inf0matics/tsp-tools-theme/commit/cc11f04))
76
+ - The MCP settings page no longer caps its own width ([65b4cbd](https://github.com/inf0matics/tsp-tools-theme/commit/65b4cbd))
77
+
78
+ ### 💅 Refactors
79
+
80
+ - Consolidate duplicated behaviour; docs move into the package ([b34e898](https://github.com/inf0matics/tsp-tools-theme/commit/b34e898))
81
+
82
+ ### 📖 Documentation
83
+
84
+ - Correct the @nuxt/icon pin notes for 2.5.1 ([43e68b7](https://github.com/inf0matics/tsp-tools-theme/commit/43e68b7))
85
+ - Reconcile README with the shipped theme; share TspToolOf ([4f72351](https://github.com/inf0matics/tsp-tools-theme/commit/4f72351))
86
+ - Generate the API reference, and document 71 undocumented props ([30a77db](https://github.com/inf0matics/tsp-tools-theme/commit/30a77db))
87
+
88
+ ### 🏡 Chore
89
+
90
+ - Check in the working agreement and dev launch config ([426dba2](https://github.com/inf0matics/tsp-tools-theme/commit/426dba2))
91
+ - Add scripts/dev.mjs so dev servers pick a free port ([996d0ef](https://github.com/inf0matics/tsp-tools-theme/commit/996d0ef))
92
+ - **release:** V0.3.0 ([0a5c793](https://github.com/inf0matics/tsp-tools-theme/commit/0a5c793))
93
+ - **playground:** Nav prototype — sub-items and section headers ([7c8938b](https://github.com/inf0matics/tsp-tools-theme/commit/7c8938b))
94
+
95
+ ### ✅ Tests
96
+
97
+ - E2e harness covering both consumption modes ([bff7d0a](https://github.com/inf0matics/tsp-tools-theme/commit/bff7d0a))
98
+
99
+ ## v0.2.1 — 2026-07-28
100
+
101
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/v0.2.0...v0.2.1)
102
+
103
+ ### 🩹 Fixes
104
+
105
+ - Cap @nuxt/icon at ~2.3.1 and gate on icon rendering ([9e4d67d](https://github.com/inf0matics/tsp-tools-theme/commit/9e4d67d))
106
+
107
+ ### 🏡 Chore
108
+
109
+ - **release:** V0.2.1 ([1d0925b](https://github.com/inf0matics/tsp-tools-theme/commit/1d0925b))
110
+
111
+ ## v0.2.0 — 2026-07-28
112
+
113
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/v0.1.3...v0.2.0)
114
+
115
+ ### 🚀 Enhancements
116
+
117
+ - Site shell, container widths, localisable theme toggle ([a16c5ff](https://github.com/inf0matics/tsp-tools-theme/commit/a16c5ff))
118
+
119
+ ### 🏡 Chore
120
+
121
+ - **release:** V0.2.0 ([0113c4c](https://github.com/inf0matics/tsp-tools-theme/commit/0113c4c))
122
+
123
+ ## v0.1.3 — 2026-07-28
124
+
125
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/v0.1.2...v0.1.3)
126
+
127
+ ### 🩹 Fixes
128
+
129
+ - Keep colour-mode assignment type-safe under color-mode v4 ([4eb970b](https://github.com/inf0matics/tsp-tools-theme/commit/4eb970b))
130
+
131
+ ### 🏡 Chore
132
+
133
+ - Stop the release script from double-bumping ([14fc551](https://github.com/inf0matics/tsp-tools-theme/commit/14fc551))
134
+ - **release:** V0.1.3 ([aa248ae](https://github.com/inf0matics/tsp-tools-theme/commit/aa248ae))
135
+
136
+ ## v0.1.2 — 2026-07-28
137
+
138
+ [compare changes](https://github.com/inf0matics/tsp-tools-theme/compare/edd310cbdf8428b32bb3472164d99e0cbaf53d79...v0.1.2)
139
+
140
+ ### 🏡 Chore
141
+
142
+ - Prepare package for npm publishing ([cde3422](https://github.com/inf0matics/tsp-tools-theme/commit/cde3422))
143
+ - **release:** V0.1.1 — exports ./package.json + CSS-ownership docs ([9629dd0](https://github.com/inf0matics/tsp-tools-theme/commit/9629dd0))
144
+ - **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,24 +44,29 @@ 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
 
37
- ### Why `@nuxt/icon` is capped at `~2.3.1`
47
+ ### Why `@nuxt/icon` is pinned at all
38
48
 
39
- The layer declares `@nuxt/icon: ~2.3.1` as a direct dependency. This is a **temporary cap**
40
- around an upstream regression, not a preference.
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.
41
51
 
42
52
  `@nuxt/icon` 2.4.1 changed its plugin to take `$fetch` from `useRequestFetch()` and then read
43
53
  `.native` off it. During SSR that returns Nitro's request-scoped `event.$fetch`, which has no
44
- `.native` property — so Iconify's fetch is set to `undefined` and **every server-side icon load
45
- fails**, logging `[Icon] failed to load icon …` per request. `@nuxt/ui` only requires
46
- `^2.3.1`, so any re-resolve of the tree silently moves consumers onto the broken version.
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.
47
62
 
48
- The cap is in `dependencies` rather than `overrides` on purpose: npm honours `overrides` only
49
- in the root project, so an `overrides` entry here would not reach consuming apps. `2.3.1`
50
- satisfies `@nuxt/ui`'s own range, so there is one hoisted copy and no duplication.
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.
51
65
 
52
- Remove the cap once a fixed `@nuxt/icon` ships. To check, drop the pin and look for
53
- `failed to load icon` in the server log of a production build — the build itself stays green
54
- either way, which is exactly why this is easy to miss.
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.
55
70
 
56
71
  ---
57
72
 
@@ -76,16 +91,28 @@ shell components wherever you like:
76
91
  <template>
77
92
  <div class="flex min-h-svh bg-default text-default">
78
93
  <TspSidebar>
79
- <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>
80
98
  <template #nav>
81
- <TspNavItem to="/" icon="i-tabler-car">Fahrten</TspNavItem>
82
- <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>
83
105
  </template>
84
106
  <template #footer>
85
- <TspSidebarFooter github-link="https://github.com/…" version="v1.2.0">
86
- <template #user>…your user link…</template>
87
- <template #logout>…your logout…</template>
88
- </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
+ />
89
116
  </template>
90
117
  </TspSidebar>
91
118
  <main class="flex-1 min-w-0 bg-default">
@@ -144,20 +171,26 @@ scope:
144
171
  |---|---|---|
145
172
  | `primary` (amber) | `#fbad18` | `#fbad18` |
146
173
  | `error` (tomato) | `#ff6347` | `#ff6347` |
147
- | `bg` | `#f8f9fa` | `#212529` |
148
- | `surface` (`bg-elevated`) | `#ffffff` | `#2b3035` |
174
+ | `bg` (page) | `#f1f3f5` | `#212529` |
175
+ | `bg-muted` (the shell) | `#f8f9fa` | `#2b3035` |
176
+ | `bg-elevated` (raised content) | `#ffffff` | `#31373e` |
149
177
  | `border` | `#dee2e6` | `#404041` |
150
178
  | `text` | `#212529` | `#f0f0f0` |
151
179
  | `on-primary` (`text-inverted`) | `#212529` | `#212529` |
152
180
 
153
- 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.
154
184
  The amber ramp is also available as Tailwind utilities (`*-tsp-amber-{50..950}`).
155
185
 
156
186
  **Fonts** — Nunito (text + headings) and Space Grotesk (logo), via `@nuxt/fonts`. Use the
157
187
  `tsp-wordmark` class (or `<TspWordmark>`) for Space Grotesk; everything else is Nunito.
158
188
 
159
- **Composable** — `useTspColorMode()` → `{ pref, toggle }`, the remembered (cookie) preference
160
- 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.
161
194
 
162
195
  **Components** (auto-imported):
163
196
 
@@ -165,16 +198,42 @@ for the themed area.
165
198
  |---|---|
166
199
  | `TspThemeProvider` | Scoped wrapper — marks a sub-area as themed (`.tsp-theme`). |
167
200
  | `TspSidebar` | App-shell sidebar frame; slots `#brand` `#nav` `#footer`. |
168
- | `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. |
169
202
  | `TspSiteHeader` | Public/marketing header; slots `#brand` `#nav` `#actions`, prop `sticky`. |
170
203
  | `TspSiteFooter` | Public/marketing footer; slots `#brand` `#links` `#actions`. |
171
204
  | `TspThemeToggle` | Sun/moon light–dark toggle (uses `useTspColorMode`). |
172
- | `TspNavItem` | Sidebar nav link (muted filled-amber when active). |
173
- | `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. |
174
226
  | `TspContainer` | Centered content column; props `width` (`app` \| `site`) and `padded`. |
175
227
 
176
- App-specific content (branding, nav lists, user identity, GitHub/version values) is injected
177
- 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.
178
237
 
179
238
  ### App shell vs site shell
180
239
 
@@ -183,20 +242,21 @@ use `TspSiteHeader` + `TspSiteFooter` with the wide container:
183
242
 
184
243
  ```vue
185
244
  <TspSiteHeader>
186
- <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>
187
246
  <template #nav><a href="#tools">Tools</a></template>
188
- <template #actions><TspThemeToggle /></template>
247
+ <template #actions><TspThemeToggle icon-only /></template>
189
248
  </TspSiteHeader>
190
249
 
191
250
  <TspContainer width="site"><NuxtPage /></TspContainer>
192
251
 
193
252
  <TspSiteFooter>
194
- <template #brand><TspWordmark>tsp<span class="text-primary">.</span>tools</TspWordmark></template>
253
+ <template #brand><TspWordmark name="trips" /></template>
195
254
  <template #links><NuxtLink to="/imprint">Imprint</NuxtLink></template>
196
255
  </TspSiteFooter>
197
256
  ```
198
257
 
199
- `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.
200
260
  `width="site"` is `max-w-[1240px]`, `px-6 md:px-10`, the marketing measure. Both add `pt-10 pb-16`
201
261
  unless you pass `:padded="false"` (which is what the header/footer frames do internally).
202
262
 
@@ -212,8 +272,10 @@ button shows and announces while dark:
212
272
  />
213
273
  ```
214
274
 
215
- The defaults are English. The default slot receives `{ isDark, label }` if you want different
216
- 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.
217
279
 
218
280
  ---
219
281
 
@@ -265,18 +327,60 @@ They are excluded from the published package (`files` allowlist).
265
327
 
266
328
  ## Release
267
329
 
268
- The version bump and the git tag are **not** done by an npm script:
330
+ One command (ADR 005):
331
+
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.
269
343
 
270
- 1. Bump `package.json` `.version`, commit, and push to `main`.
271
- 2. Publish a GitHub Release for that commit — GitHub creates the `vX.Y.Z` tag on publish.
272
- 3. Ship the package:
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
+ **There is no provenance, and that is not an oversight.** npm only signs provenance for a
362
+ **public** source repository; this one is private, so npm skips the attestation. Asking for it
363
+ explicitly does not produce one — it fails the publish with a 422 (`Unsupported GitHub Actions
364
+ source repository visibility: "private"`), which is how the first `v0.3.1` attempt died. If this
365
+ repository is ever made public, set `publishConfig.provenance` and restore the workflow step that
366
+ checks the attestation actually landed.
367
+
368
+ Trusted publishing is unaffected and is the substantive win either way: there is no token to
369
+ leak, and the registry records the publisher as GitHub Actions over OIDC rather than a person.
370
+
371
+ **`0.3.0` is a special case: it needs a tag, not a bump.** Its version was already written into
372
+ `package.json` by hand (`0a5c793`) and its changelog entry is written, so two of changelogen's
373
+ three jobs are done. The third — `changelogen --release` — cannot run: it refuses a bump whose
374
+ result equals the current version (`bumpVersion` returns `false`, reported as "Unable to bump
375
+ version based on changes"), and `-r 0.3.0` on a package already at `0.3.0` is exactly that. So
376
+ tag it directly:
273
377
 
274
- ```bash
275
- npm run release # lint, then npm publish
276
- ```
378
+ ```bash
379
+ npm run gate && git tag -a v0.3.0 -m "v0.3.0" && git push --follow-tags
380
+ ```
277
381
 
278
- `npm run release` deliberately only lints and publishes. It never bumps the version or tags,
279
- so it can't drift from the version that was already committed and tagged in steps 1–2.
382
+ From `0.3.1` on, `npm run bump-version` is the path changelogen picks the next version from the
383
+ commits, so there is nothing to force.
280
384
 
281
385
  ## License
282
386
 
@@ -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>