@uxfront/layer-docs 0.4.1 → 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 (99) hide show
  1. package/README.md +31 -220
  2. package/app/app.config.ts +8 -92
  3. package/app/components/content/FrameworkSwitcher.vue +66 -40
  4. package/app/components/docs/DocsAsideLeftTop.vue +9 -15
  5. package/app/components/docs/DocsFrameworkSelect.vue +4 -7
  6. package/app/composables/useFramework.ts +30 -38
  7. package/nuxt.config.ts +14 -170
  8. package/package.json +10 -77
  9. package/CHANGELOG.md +0 -192
  10. package/LICENSE +0 -21
  11. package/app/app.vue +0 -138
  12. package/app/assets/css/main.css +0 -15
  13. package/app/components/IconMenuToggle.vue +0 -92
  14. package/app/components/LanguageSelect.vue +0 -73
  15. package/app/components/MorphingGradientBackground.vue +0 -261
  16. package/app/components/OgImage/OgImageDocs.satori.vue +0 -40
  17. package/app/components/OgImage/OgImageLanding.satori.vue +0 -41
  18. package/app/components/app/AppFooter.vue +0 -13
  19. package/app/components/app/AppFooterCenter.vue +0 -17
  20. package/app/components/app/AppFooterLeft.vue +0 -21
  21. package/app/components/app/AppFooterRight.vue +0 -33
  22. package/app/components/app/AppHeader.vue +0 -123
  23. package/app/components/app/AppHeaderAttribution.vue +0 -45
  24. package/app/components/app/AppHeaderBody.vue +0 -14
  25. package/app/components/app/AppHeaderCTA.vue +0 -31
  26. package/app/components/app/AppHeaderCenter.vue +0 -10
  27. package/app/components/app/AppHeaderLogo.vue +0 -16
  28. package/app/components/app/AppOgDecoration.vue +0 -27
  29. package/app/components/app/AppOgLogo.vue +0 -19
  30. package/app/components/app/AppSearch.vue +0 -59
  31. package/app/components/app/AppSubHeader.vue +0 -21
  32. package/app/components/content/BrowserFrame.vue +0 -28
  33. package/app/components/content/GradientPageHero.vue +0 -35
  34. package/app/components/content/StorybookEmbed.vue +0 -160
  35. package/app/components/content/Video.vue +0 -103
  36. package/app/components/docs/DocsAsideLeftBody.vue +0 -20
  37. package/app/components/docs/DocsAsideRightBottom.vue +0 -15
  38. package/app/components/docs/DocsPageHeaderLinks.vue +0 -75
  39. package/app/composables/useDocsSections.ts +0 -57
  40. package/app/composables/useDocusI18n.ts +0 -49
  41. package/app/constants/sections.ts +0 -25
  42. package/app/error.vue +0 -140
  43. package/app/layouts/default.vue +0 -24
  44. package/app/pages/[[lang]]/[...slug].vue +0 -58
  45. package/app/pages/[[lang]]/docs/[section]/[...slug].vue +0 -180
  46. package/app/plugins/i18n.ts +0 -21
  47. package/app/plugins/posthog.client.ts +0 -56
  48. package/app/types/non-route-categories.ts +0 -12
  49. package/app/utils/flattenNavigation.ts +0 -22
  50. package/app/utils/foldNonRouteCategories.ts +0 -47
  51. package/app/utils/prerender.ts +0 -9
  52. package/app/utils/storybookEmbed.test.ts +0 -98
  53. package/app/utils/storybookEmbed.ts +0 -93
  54. package/i18n/locales/ar.json +0 -24
  55. package/i18n/locales/be.json +0 -24
  56. package/i18n/locales/bn.json +0 -24
  57. package/i18n/locales/ca.json +0 -24
  58. package/i18n/locales/ckb.json +0 -24
  59. package/i18n/locales/cs.json +0 -24
  60. package/i18n/locales/da.json +0 -24
  61. package/i18n/locales/de.json +0 -24
  62. package/i18n/locales/el.json +0 -24
  63. package/i18n/locales/en.json +0 -24
  64. package/i18n/locales/et.json +0 -24
  65. package/i18n/locales/fr.json +0 -24
  66. package/i18n/locales/he.json +0 -24
  67. package/i18n/locales/hi.json +0 -24
  68. package/i18n/locales/hy.json +0 -24
  69. package/i18n/locales/it.json +0 -24
  70. package/i18n/locales/ja.json +0 -24
  71. package/i18n/locales/kk.json +0 -24
  72. package/i18n/locales/km.json +0 -24
  73. package/i18n/locales/ko.json +0 -24
  74. package/i18n/locales/ky.json +0 -24
  75. package/i18n/locales/lb.json +0 -24
  76. package/i18n/locales/ms.json +0 -24
  77. package/i18n/locales/nb.json +0 -24
  78. package/i18n/locales/pl.json +0 -24
  79. package/i18n/locales/ru.json +0 -24
  80. package/i18n/locales/sl.json +0 -24
  81. package/i18n/locales/sv.json +0 -24
  82. package/i18n/locales/uk.json +0 -24
  83. package/i18n/locales/ur.json +0 -24
  84. package/i18n/locales/vi.json +0 -24
  85. package/modules/config.ts +0 -144
  86. package/modules/optimizeDeps.ts +0 -45
  87. package/modules/routing.ts +0 -20
  88. package/nuxt.schema.ts +0 -374
  89. package/server/plugins/llms-redirect.ts +0 -60
  90. package/server/routes/raw/[...slug].md.get.ts +0 -74
  91. package/storybook/index.test.ts +0 -110
  92. package/storybook/index.ts +0 -362
  93. package/test/brand-palette.ts +0 -235
  94. package/test/no-brand-leakage.test.ts +0 -124
  95. package/tsconfig.json +0 -17
  96. package/utils/accent.ts +0 -80
  97. package/utils/content.ts +0 -193
  98. package/utils/git.ts +0 -114
  99. package/utils/meta.ts +0 -28
package/nuxt.config.ts CHANGED
@@ -1,181 +1,25 @@
1
- import { createResolver, useNuxt } from "@nuxt/kit";
2
-
3
- const { resolve } = createResolver(import.meta.url);
4
-
5
1
  /**
6
- * `@uxfront/layer-docs` — a neutral, brandable Nuxt-layer documentation theme.
7
- *
8
- * Consumers extend it via `extends: ['@uxfront/layer-docs']` and supply their
9
- * own branding (title, logos, socials, palette) through their `app.config.ts`,
10
- * merged over the layer's neutral defaults by Nuxt's `defu` layer merge. The
11
- * section topology (`DOCS_SECTIONS`) and markdown content stay in the consumer.
12
- *
13
- * i18n is OFF by default: `@nuxtjs/i18n` is not registered here, so a v1 single
14
- * locale site "just works". A consumer that wants localisation registers the
15
- * module itself; `modules/config` + `useDocusI18n` pick it up automatically.
16
- *
17
- * ## Styling: the consumer owns the single Tailwind entry
18
- *
19
- * This layer deliberately does NOT register `app/assets/css/main.css` in `css`.
20
- * Its base is palette-free, so a consumer's brand `@theme` only compiles if it
21
- * lives inside the same Tailwind pass — which means the consumer's CSS file has
22
- * to *import* this base, not sit beside it. Registering it here as well gave
23
- * every consumer two Tailwind entries and a byte-for-byte duplicate of every
24
- * base utility in the shipped stylesheet (+26.7 KB gzip on inkline, UXF-118),
25
- * and left each consumer to un-register what the layer had just registered.
2
+ * `@uxfront/layer-docs`: the Nuxt side of UXFront's documentation sites.
26
3
  *
27
- * Consumers therefore register exactly one CSS entry, their own:
28
- *
29
- * ```ts
30
- * // nuxt.config.ts
31
- * css: ["./app/assets/css/main.css"],
32
- * ```
33
- *
34
- * whose first line imports this base, followed by the brand `@theme`. Both
35
- * halves are guarded by `@uxfront/layer-docs/test`; see README § Styling.
4
+ * It extends Docus, which renders `content/docs/` with its header, sidebar,
5
+ * search and table of contents, and adds a framework switcher on top: a
6
+ * `::framework-switcher` content component with one slot per framework, and a
7
+ * Framework select above the sidebar. The app lists its frameworks in
8
+ * `docsTheme.frameworks` in its `app.config.ts`.
36
9
  *
37
10
  * https://nuxt.com/docs/getting-started/layers
38
11
  */
39
12
  export default defineNuxtConfig({
40
- compatibilityDate: "2025-07-22",
41
- telemetry: false,
42
- // Expose `app/constants/` (DOCS_SECTIONS, findDocsSectionBySlug) as
43
- // auto-imports. The layer ships a neutral empty default; a consumer's own
44
- // `app/constants/` overrides it via auto-import dir precedence.
45
- imports: {
46
- dirs: ["constants"],
47
- },
48
- modules: [
49
- resolve("./modules/config"),
50
- resolve("./modules/routing"),
51
- resolve("./modules/optimizeDeps"),
52
- "@nuxt/ui",
53
- "@nuxt/image",
54
- "@nuxt/scripts",
55
- "@nuxtjs/robots",
56
- "@nuxtjs/sitemap",
57
- "@nuxt/content",
58
- "nuxt-llms",
59
- "nuxt-og-image",
60
- ],
61
- icon: {
62
- serverBundle: "local",
63
- fetchTimeout: 10000,
64
- },
65
- // Syntax-highlighting language set + MDC auto-unwrap. Shared across every
66
- // docs consumer; brand-agnostic.
67
- content: {
68
- build: {
69
- markdown: {
70
- highlight: {
71
- langs: [
72
- "bash",
73
- "diff",
74
- "json",
75
- "js",
76
- "ts",
77
- "tsx",
78
- "html",
79
- "css",
80
- "vue",
81
- "svelte",
82
- "astro",
83
- "shell",
84
- "mdc",
85
- "md",
86
- "yaml",
87
- ],
88
- },
89
- remarkPlugins: {
90
- "remark-mdc": {
91
- options: {
92
- autoUnwrap: true,
93
- },
94
- },
95
- },
96
- },
97
- },
98
- },
99
- nitro: {
100
- prerender: {
101
- crawlLinks: true,
102
- failOnError: false,
103
- autoSubfolderIndex: false,
104
- },
105
- },
106
- hooks: {
107
- // Seed the prerender crawl with the site root(s). Without i18n that's just
108
- // "/"; with i18n it's each locale root ("/en", "/fr", ...). Everything else
109
- // is reached via crawlLinks.
110
- "nitro:config"(nitroConfig) {
111
- const nuxt = useNuxt();
112
- const i18nOptions = nuxt.options.i18n as
113
- | { locales?: Array<string | { code: string }> }
114
- | undefined;
115
-
116
- const routes: string[] = [];
117
- if (!i18nOptions?.locales?.length) {
118
- routes.push("/");
119
- } else {
120
- routes.push(
121
- ...i18nOptions.locales.map((locale) =>
122
- typeof locale === "string" ? `/${locale}` : `/${locale.code}`,
123
- ),
124
- );
125
- }
13
+ extends: ["docus"],
126
14
 
127
- nitroConfig.prerender = nitroConfig.prerender || {};
128
- nitroConfig.prerender.routes = nitroConfig.prerender.routes || [];
129
- nitroConfig.prerender.routes.push(...routes);
130
- },
131
- },
132
- /**
133
- * OG images are on by default — same class of SEO plumbing as sitemap and
134
- * robots, which this layer also registers unasked. A consumer opts out with
135
- * the module's own `ogImage: { enabled: false }`; no layer-specific key.
136
- *
137
- * Prerendering bakes every card at build time, so no runtime generation
138
- * endpoint is needed (or exposed unsigned).
139
- * @docs https://nuxtseo.com/og-image/guides/zero-runtime
140
- */
141
- ogImage: {
142
- zeroRuntime: true,
143
- // 1.91:1, the ratio every major crawler crops to. The module's own default
144
- // is 1200×600 (2:1), which gets letterboxed or trimmed on most previews.
145
- defaults: {
146
- width: 1200,
147
- height: 630,
148
- },
149
- },
150
- // Generic sitemap defaults; brand values (url/name) come from the consumer's
151
- // `site` config or are inferred in `modules/config`.
152
- sitemap: {
153
- enabled: true,
154
- discoverImages: true,
155
- discoverVideos: true,
156
- },
157
- // Neutral analytics defaults. The PostHog client plugin (ships in this layer)
158
- // only initialises in production when `app.config` `analytics.enabled` is true
159
- // and a key is present; consumers override the key/host from their own env.
160
- runtimeConfig: {
161
- public: {
162
- posthog: {
163
- key: "",
164
- host: "https://us.i.posthog.com",
165
- defaults: "2025-05-24",
15
+ icon: {
16
+ clientBundle: {
17
+ scan: {
18
+ // Nuxt Icon's default, plus app config files, where `docsTheme.frameworks`
19
+ // names its icons. The sidebar's select shows them on every page, so
20
+ // without this, pages with no switcher fetch them from the Iconify API.
21
+ globInclude: ["**/*.{vue,jsx,tsx,md,mdc,mdx,yml,yaml}", "**/app.config.{ts,mts,js,mjs}"],
166
22
  },
167
- // Where `StorybookEmbed` points. Empty here — the host is a consumer
168
- // fact, and where a Storybook is deployed changes without a code change,
169
- // so this is runtime config (`NUXT_PUBLIC_STORYBOOK_BASE_URL`) rather
170
- // than app config. A `{framework}` placeholder addresses per-framework
171
- // deployments; a template without one is used as-is.
172
- storybookBaseUrl: "",
173
- // Migration-only: a brand namespace whose `<ns>:theme` / `<ns>:height`
174
- // messages a consumer's Storybook already speaks. Set it and the embed
175
- // emits and accepts both those names and the neutral ones, so the docs
176
- // site and the Storybook can deploy in either order. Unset it once both
177
- // sides are on `@uxfront/layer-docs/storybook`.
178
- storybookLegacyMessageNamespace: "",
179
23
  },
180
24
  },
181
25
  });
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@uxfront/layer-docs",
3
- "version": "0.4.1",
4
- "description": "Neutral, brandable Nuxt-layer documentation theme. Consumers extend it and supply their own branding, content and section topology.",
3
+ "version": "0.5.0",
4
+ "description": "Nuxt layer for UXFront documentation sites: Docus, plus a framework switcher that shows every reader the examples for their framework.",
5
5
  "keywords": [
6
6
  "docs",
7
7
  "documentation",
8
8
  "docus",
9
9
  "nuxt",
10
- "nuxt-layer",
11
- "theme"
10
+ "nuxt-layer"
12
11
  ],
13
12
  "homepage": "https://github.com/uxfront-com/uxfront/tree/main/packages/layer-docs#readme",
14
13
  "bugs": "https://github.com/uxfront-com/uxfront/issues",
@@ -20,18 +19,8 @@
20
19
  },
21
20
  "files": [
22
21
  "app",
23
- "i18n",
24
- "modules",
25
- "server",
26
- "storybook",
27
- "test",
28
- "utils",
29
22
  "nuxt.config.ts",
30
- "nuxt.schema.ts",
31
- "tsconfig.json",
32
- "README.md",
33
- "CHANGELOG.md",
34
- "LICENSE"
23
+ "README.md"
35
24
  ],
36
25
  "type": "module",
37
26
  "main": "./nuxt.config.ts",
@@ -41,77 +30,21 @@
41
30
  "types": "./nuxt.config.ts",
42
31
  "import": "./nuxt.config.ts"
43
32
  },
44
- "./content": {
45
- "types": "./utils/content.ts",
46
- "import": "./utils/content.ts"
47
- },
48
- "./storybook": {
49
- "types": "./storybook/index.ts",
50
- "import": "./storybook/index.ts"
51
- },
52
- "./test": {
53
- "types": "./test/brand-palette.ts",
54
- "import": "./test/brand-palette.ts"
55
- },
56
- "./app/assets/css/main.css": "./app/assets/css/main.css",
57
33
  "./package.json": "./package.json"
58
34
  },
59
35
  "publishConfig": {
60
36
  "access": "public"
61
37
  },
62
38
  "dependencies": {
63
- "@vueuse/core": "^14.3.0",
64
- "defu": "^6.1.4",
65
- "git-url-parse": "^16.1.0",
66
- "minimark": "^0.2.0",
67
- "motion-v": "^1.7.2",
68
- "pkg-types": "^2.3.1",
69
- "scule": "^1.3.0",
70
- "ufo": "^1.6.4"
39
+ "@vueuse/core": "^14.4.0",
40
+ "docus": "^5.13.0"
71
41
  },
72
42
  "devDependencies": {
73
- "typescript": "^6.0.3",
74
- "vitest": "^4.1.10"
43
+ "nuxt": "^4.5.2",
44
+ "vue": "^3.5.43"
75
45
  },
76
46
  "peerDependencies": {
77
- "@nuxt/content": "^3.14.0",
78
- "@nuxt/image": "^1.11.0",
79
- "@nuxt/kit": "^4.4.8",
80
- "@nuxt/scripts": "^0.11.13",
81
- "@nuxt/ui": "^4.8.2",
82
- "@nuxtjs/i18n": "^10.4.0",
83
- "@nuxtjs/mdc": "^0.22.0",
84
- "@nuxtjs/robots": "^6.1.1",
85
- "@nuxtjs/sitemap": "^8.2.1",
86
- "@resvg/resvg-js": "^2.6.0",
87
- "nuxt": "^4.4.8",
88
- "nuxt-llms": "^0.2.0",
89
- "nuxt-og-image": "^6.6.0",
90
- "posthog-js": "^1.386.6",
91
- "satori": "^0.26.0",
92
- "tailwindcss": "^4.3.1",
93
- "typescript": "^6.0.3",
94
- "vitest": "^4.1.10",
95
- "vue": "^3.5.38"
96
- },
97
- "peerDependenciesMeta": {
98
- "@nuxtjs/i18n": {
99
- "optional": true
100
- },
101
- "@nuxtjs/mdc": {
102
- "optional": true
103
- },
104
- "posthog-js": {
105
- "optional": true
106
- },
107
- "typescript": {
108
- "optional": true
109
- },
110
- "vitest": {
111
- "optional": true
112
- }
113
- },
114
- "scripts": {
115
- "postinstall": "nuxt prepare"
47
+ "nuxt": "^4.5.0",
48
+ "vue": "^3.5.0"
116
49
  }
117
50
  }
package/CHANGELOG.md DELETED
@@ -1,192 +0,0 @@
1
- # @uxfront/layer-docs
2
-
3
- ## 0.4.1
4
-
5
- ### Patch Changes
6
-
7
- - Republish the `0.4.0` code through automated release tooling. No code changes.
8
-
9
- `0.4.0` and every version before it were published by hand from a maintainer's machine. Nothing linked a version on the registry back to the commit it was built from, and the release depended on a long-lived npm token.
10
-
11
- `0.4.1` is the first version published from CI, by Changesets, over npm trusted publishing. No npm token takes part: the registry credential is minted for each run from a GitHub OIDC token. Every shipped file is byte-identical to `0.4.0` except this changelog entry and the `version` field.
12
-
13
- This release carries **no provenance attestation**. npm does not generate provenance for a package built from a private source repository, and `uxfront` is private. Provenance becomes available if the repository becomes public. It is not a property of this version.
14
-
15
- ## 0.4.0
16
-
17
- ### Minor Changes
18
-
19
- - a4caf06: Ship OG image support and the two satori card templates.
20
-
21
- Every consumer was registering `nuxt-og-image` itself and carrying its own copy of the templates, which meant carrying a page override too — `defineOgImage` cannot be called from the layer if the module is not registered by it. The layer now registers the module beside `@nuxtjs/sitemap` and `@nuxtjs/robots` (same class of SEO plumbing it already turns on unasked) and calls `defineOgImage` from its own docs and landing pages, so a consumer that extends the layer gets branded cards without writing anything. Opt out with the module's own switch, `ogImage: { enabled: false }`.
22
-
23
- **The accent is a brand token, not a hardcoded colour.** The existing copies hardcoded a Tailwind class, which was wrong in a way that only showed up on the rendered card: every consumer shadows stock Tailwind scale names with its own values in `@theme`, so `text-teal-500` renders Tailwind's teal, not the brand's. Custom properties are not an option either — satori has no CSS cascade, and `var(--ui-primary)` resolves to nothing. The accent is therefore resolved to a literal at build time by following `--ui-primary` to the `--color-<scale>` declaration it aliases in the consumer's registered CSS, the same two hops the browser does and the same pair `describeBrandPaletteCss` already asserts. Override it with a literal via `ogImage.accent` in `app.config.ts`.
24
-
25
- Two parts of the card are components rather than props, because `defineOgImage` serialises its arguments and a Vue slot cannot cross that boundary. Shadow `app/AppOgDecoration.vue` (a neutral radial wash) or `app/AppOgLogo.vue` (a wordmark from `header.title`) at the same path to replace either.
26
-
27
- Cards render at 1200×630 rather than the module's 1200×600 default — 1.91:1 is the ratio crawlers crop to.
28
-
29
- New peer dependencies: `nuxt-og-image`, `satori`, and `@resvg/resvg-js`. Satori renders the template to SVG and needs a separate rasteriser for the PNG; without one, prerendering logs `renderer.createImage error` per card and emits nothing.
30
-
31
- - 5cc11f2: Add `StorybookEmbed` and the `@uxfront/layer-docs/storybook` bridge.
32
-
33
- `StorybookEmbed` embeds a deployed Storybook story in a docs page, in one of
34
- three modes — `preview` (canvas only), `full` (manager without the sidebar), and
35
- `panel` (manager with the controls panel). It mounts lazily, keeps the
36
- Storybook's colour mode in sync with the page's, and grows to fit its story
37
- without shifting layout. The Storybook host comes from
38
- `NUXT_PUBLIC_STORYBOOK_BASE_URL`, with an optional `{framework}` placeholder for
39
- per-framework deployments.
40
-
41
- The `/storybook` subpath export ships the other half of that contract:
42
- `installDocsEmbedPreviewBridge` and `installDocsEmbedManagerBridge`, which are
43
- framework-free and import nothing from Nuxt. A Storybook that already speaks a
44
- branded message namespace can name it via
45
- `NUXT_PUBLIC_STORYBOOK_LEGACY_MESSAGE_NAMESPACE`, and both sides accept and emit
46
- both spellings so the two can deploy in either order.
47
-
48
- ## 0.3.0
49
-
50
- ### Minor Changes
51
-
52
- - 751810d: Stop registering the layer's own `main.css`, and ship the brand-palette guards consumers were each expected to write themselves.
53
-
54
- **Breaking for any consumer that does not already register its own CSS entry.** The layer registered `app/assets/css/main.css` in `css` while every consumer also imported that same base from its brand CSS — because a brand `@theme` only compiles into real `:root` custom properties when it lives inside a Tailwind pass, which means importing the base rather than sitting beside it. Two entries, two Tailwind passes, and a byte-for-byte duplicate of every base utility in the shipped stylesheet: **+212 KB raw / +26.7 KB gzip (+94%)** on inkline's render-blocking `entry.css`, on every page, for every visitor (UXF-118).
55
-
56
- The layer no longer registers it. Consumers register exactly one CSS entry of their own, whose first line imports the layer base — which is what all three consumers were already doing, one of them via a `modules:done` hook that un-registered what the layer had just registered. That hook can now be deleted.
57
-
58
- **Migration.** If your app already has `css: ["./app/assets/css/main.css"]` pointing at a file that starts with `@import "@uxfront/layer-docs/app/assets/css/main.css"`, nothing changes except that your stylesheet halves; drop any hook that stripped the layer's entry. If it does not, add both — see README § Styling. There is no silent middle state: without a CSS entry the app ships no Tailwind utilities at all.
59
-
60
- **What the duplication actually cost.** Payload, not layout. The duplicate pass measured on inkline was a complete superset of the first and was emitted wholly after it, so the last `sm:`/`lg:` variant still landed after the last conflicting base and won by source order — computed-style A/B across 4 pages × 4 viewports found zero rendering difference. That rescue was incidental rather than designed, which is why the cause is fixed rather than the symptom.
61
-
62
- **New: `@uxfront/layer-docs/test`.** The two guards that would have caught this before ship, now parameterised by brand scale instead of copy-pasted per repo. `describeBrandPaletteCss` asserts, without a build, that your CSS entry imports the layer base before its `@theme` and defines the scale it maps onto `--ui-primary`. `describeSingleTailwindPass` reads the built stylesheet and asserts every base utility is emitted **exactly once** — catching a duplicate pass and a CSS entry that never reached the bundle with the same assertion. `vitest` is an optional peer dependency, needed only for these.
63
-
64
- Also fixed: three code comments claimed the second pass "kills every `sm:`/`lg:` responsive variant". It does not, in the equal-superset case measured; the guards now describe the duplicate payload they actually guard.
65
-
66
- ## 0.2.1
67
-
68
- ### Patch Changes
69
-
70
- - Stop the optional `posthog-js` peer from killing the client bundle of every consumer that omits it.
71
-
72
- `app/plugins/posthog.client.ts` imported `posthog-js` statically at module scope while declaring it an **optional** `peerDependency`. Rollup has no module to resolve for a consumer that skipped the optional peer, so it emits the specifier as a module-scope throw:
73
-
74
- ```js
75
- const HB = {};
76
- throw new Error('Could not resolve "posthog-js" imported by "@uxfront/layer-docs".');
77
- ```
78
-
79
- That throw runs on evaluation, before the plugin body — so neither the `analytics.enabled` guard nor the `posthog.key` guard ever executed, and the failure was not confined to analytics. The whole client entry died with it: **the site did not hydrate at all**. Every page served as a dead static document — theme toggle stuck on its loading skeleton, framework tabs inert, no client-side routing, `localStorage` never written — with one console error per route as the only signal. Present in `0.1.0`, `0.1.1` and `0.2.0`.
80
-
81
- `posthog-js` is now imported dynamically, inside the branch that has already confirmed analytics is enabled and a key is provisioned. The unresolvable specifier moves out of the client entry into its own lazy chunk that a consumer without the peer never evaluates — it is emitted, prefetched at most, and never run. The import is also wrapped, which keeps the failure proportional to what failed: a missing or broken module degrades to a console warning and no analytics, instead of a dead app.
82
-
83
- `modules/optimizeDeps` carried the same assumption from the other direction — it named `posthog-js` in `optimizeDeps.include` unconditionally, which is a hard dev-server error when the package is not installed. The hint is now added only when the dependency actually resolves.
84
-
85
- Consumers who do not use PostHog need no changes and should upgrade. Consumers who do use it are unaffected: `$posthog()` is provided exactly as before once the client initialises.
86
-
87
- ## 0.2.0
88
-
89
- ### Minor Changes
90
-
91
- - Absorb documentation-site plumbing that consumers were re-declaring by hand.
92
-
93
- - `defineDocsCollections(sections, options)` gains `{ sitemap?, changelog? }`. With
94
- `sitemap: true` the landing and docs collections carry `defineSitemapSchema()`;
95
- with `changelog: true` a `changelog` collection is registered over
96
- `changelog/*.md`. Both default to `false`, so existing single-argument calls are
97
- unchanged. Per-locale collections are now gated on `locales.length > 0` rather
98
- than the array's mere presence.
99
- - 30 additional locales ship with the layer (ar, be, bn, ca, ckb, cs, da, de, el,
100
- et, fr, he, hi, hy, it, ja, kk, km, ko, ky, lb, ms, nb, pl, ru, sl, sv, uk, ur,
101
- vi), each complete against `en.json`.
102
- - `nuxt.schema.ts` ships with the layer, so the Content Studio preview schema for
103
- `app.config.ts` is described once instead of per consumer.
104
- - New `modules/optimizeDeps` module pre-bundles the layer's own heavier runtime
105
- dependencies; registered automatically.
106
- - New `DocsAsideLeftTop` / `DocsFrameworkSelect` render a persistent framework
107
- selector above the sidebar navigation, and `GradientPageHero` puts `UPageHero`
108
- on `MorphingGradientBackground`. Both render only when the consumer opts in.
109
-
110
- **Breaking-ish:** `useFramework()` no longer ships a built-in React/Vue/Vanilla
111
- default list. `frameworks` is now `appConfig.docsTheme.frameworks ?? []`, making
112
- app config the single source of truth. Sites that relied on the implicit default
113
- must declare `docsTheme.frameworks` in their own `app.config.ts`; `FrameworkSwitcher`
114
- renders no tab bar when the list is empty.
115
-
116
- ### Patch Changes
117
-
118
- - Correct the changelog so it matches what was actually published. `0.1.1` shipped
119
- with no entry of its own while an entry for a `0.1.2` that was never published
120
- sat at the top of the file, so consumers had no way to learn from the changelog
121
- that `0.1.1` contains accessibility fixes for `button-name` and
122
- `nested-interactive` violations. `0.1.1` now has a complete entry, and the
123
- `AppHeaderCTA.vue` typecheck fix previously filed under `0.1.2` is recorded
124
- under `0.1.0`, the release it actually shipped in.
125
-
126
- Note: `0.2.0` was versioned by a hand `chore: bump version` commit that bypassed
127
- `changeset version`, so it published with no changelog entry at all — the tarball
128
- topped out at a `## 0.1.2` heading for a version that was versioned but never
129
- published. The entry above is the accurate, reconstructed record of what `0.2.0`
130
- actually contains, and the changelog-only correction previously filed under
131
- `0.1.2` is folded in here because `0.2.0` is the release it shipped in. Every
132
- heading in this file now corresponds to a version that exists on the registry.
133
- Second occurrence of UXF-67; see UXF-168.
134
-
135
- ## 0.1.1
136
-
137
- ### Minor Changes
138
-
139
- - Add `header.attribution` so a consuming app can render a partially-linked
140
- wordmark, e.g. "uxd by UXFront" where only "UXFront" links out. The header now
141
- owns UHeader's `#left` slot instead of `#title`, because `#title` is rendered
142
- inside UHeader's own home link and an attribution link there would nest
143
- anchors. Apps that do not set `attribution` render exactly as before.
144
-
145
- ### Patch Changes
146
-
147
- - Fix accessible names on icon-only navigation controls and hide the section
148
- sub-header when there is nothing to switch between.
149
-
150
- - The mobile menu toggle and the "copy page" dropdown trigger now expose
151
- accessible names (sourced from the active locale), clearing two critical
152
- `button-name` violations that shipped on every page. **Consumers on `0.1.0`
153
- should upgrade** — those violations were present on every page of every
154
- site extending this layer.
155
- - The page footer separator is now `decorative`, clearing a serious
156
- `nested-interactive` violation caused by focusable links inside an element
157
- with `role="separator"`.
158
- - The docs sub-header no longer renders when the consumer declares one section
159
- or none, so a single-section site gets no empty bar and no extra header
160
- height.
161
-
162
- - Skip PostHog initialisation when no key is provisioned
163
- (`NUXT_PUBLIC_POSTHOG_KEY` unset), instead of initialising a client that fires
164
- requests at nothing.
165
-
166
- Note: `0.1.1` was versioned by hand rather than through `changeset version`, so
167
- it published as a patch even though `header.attribution` is additive and would
168
- have produced `0.2.0`. The published number stands; the entry above is the
169
- accurate record of its contents. See UXF-67.
170
-
171
- ## 0.1.0
172
-
173
- ### Minor Changes
174
-
175
- - Initial scaffold of the neutral Nuxt-layer docs theme: auto-config module
176
- (site metadata / SEO / git inference), neutral `app.config` defaults and UI
177
- polish, base `main.css` (Tailwind + Nuxt UI wiring, palette-free),
178
- `MorphingGradientBackground`, and opt-out-gated i18n-redirect and PostHog
179
- plugins. Nuxt documentation stack declared as pinned `peerDependencies`.
180
-
181
- ### Patch Changes
182
-
183
- - Fix `nuxt typecheck` failure in `AppHeaderCTA.vue`: the config module seeds a
184
- partial `header` (`{ title }`), so defu narrows the resolved `AppConfig` header
185
- to `{ title, logo }` and drops the array-valued `links`. Read `links` through a
186
- cast on `header` itself instead of the `.links` result, so consumers extending
187
- the layer typecheck cleanly. Runtime behaviour is unchanged.
188
-
189
- This fix was previously filed under a `## 0.1.2` heading that no published
190
- release ever carried. It was written while the package was still named
191
- `@uxfront/docs-theme` — a name that was never published — and it shipped in
192
- `@uxfront/layer-docs@0.1.0`, the first release under the current name.
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 uxfront
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/app/app.vue DELETED
@@ -1,138 +0,0 @@
1
- <script setup lang="ts">
2
- import type { ContentNavigationItem, PageCollections } from "@nuxt/content";
3
- import * as nuxtUiLocales from "@nuxt/ui/locale";
4
-
5
- const appConfig = useAppConfig();
6
- const { seo } = appConfig;
7
- const site = useSiteConfig();
8
- const { locale, locales, isEnabled, switchLocalePath } = useDocusI18n();
9
-
10
- const lang = computed(
11
- () => nuxtUiLocales[locale.value as keyof typeof nuxtUiLocales]?.code || "en",
12
- );
13
- const dir = computed(() => nuxtUiLocales[locale.value as keyof typeof nuxtUiLocales]?.dir || "ltr");
14
-
15
- const getCollectionName = (key: string) =>
16
- (isEnabled.value ? `docs_${key}_${locale.value}` : `docs_${key}`) as keyof PageCollections;
17
-
18
- useHead({
19
- meta: [{ name: "viewport", content: "width=device-width, initial-scale=1" }],
20
- link: [{ rel: "icon", href: "/favicon.ico" }],
21
- htmlAttrs: {
22
- lang,
23
- dir,
24
- },
25
- });
26
-
27
- useSeoMeta({
28
- titleTemplate: seo?.titleTemplate,
29
- title: seo?.title,
30
- description: seo?.description,
31
- ogSiteName: site.name,
32
- twitterCard: "summary_large_image",
33
- });
34
-
35
- if (isEnabled.value) {
36
- const route = useRoute();
37
- const defaultLocale = (useRuntimeConfig().public.i18n as { defaultLocale: string }).defaultLocale;
38
- onMounted(() => {
39
- const currentLocale = route.path.split("/")[1];
40
- if (!locales.some((locale) => locale.code === currentLocale)) {
41
- return navigateTo(switchLocalePath(defaultLocale) as string);
42
- }
43
- });
44
- }
45
-
46
- const { data: navigation } = await useAsyncData(
47
- () => `navigation_${locale.value}`,
48
- async () => {
49
- const results = await Promise.all(
50
- DOCS_SECTIONS.map(async (section) => {
51
- const collectionName = getCollectionName(section.key);
52
- const data = await queryCollectionNavigation(collectionName);
53
- const rootResult =
54
- data.find((item) => item.path === `/docs/${section.slug}`)?.children ||
55
- data.find((item) => item.path === "/docs")?.children ||
56
- data ||
57
- [];
58
- const localeResult =
59
- rootResult.find((item) => item.path === `/${locale.value}`)?.children || rootResult;
60
- const sectionPath = `/docs/${section.slug}`;
61
- let result: ContentNavigationItem[];
62
- if (Array.isArray(section.folder)) {
63
- const rootIdx = "rootFolder" in section ? (section.rootFolder as number) : -1;
64
- const rootWrapper =
65
- localeResult.length === 1 && localeResult[0]?.path === sectionPath
66
- ? localeResult[0]
67
- : null;
68
- const allItems = rootWrapper ? (rootWrapper.children ?? []) : localeResult;
69
-
70
- if (rootIdx >= 0) {
71
- const nonRootPaths = new Set(
72
- section.folder
73
- .filter((_: string, i: number) => i !== rootIdx)
74
- .map((f: string) => `${sectionPath}/${f.replace(/^\d+\./, "")}`),
75
- );
76
- const items: ContentNavigationItem[] = [];
77
- for (let i = 0; i < section.folder.length; i++) {
78
- if (i === rootIdx) {
79
- items.push({
80
- ...rootWrapper,
81
- title: rootWrapper?.title ?? section.label,
82
- path: sectionPath,
83
- children: allItems.filter((item) => !nonRootPaths.has(item.path)),
84
- } as ContentNavigationItem);
85
- } else {
86
- const found = allItems.find(
87
- (item) =>
88
- item.path === `${sectionPath}/${section.folder[i]?.replace(/^\d+\./, "")}`,
89
- );
90
- if (found) items.push(found);
91
- }
92
- }
93
- result = items;
94
- } else {
95
- result = section.folder
96
- .map((folder: string) =>
97
- allItems.find(
98
- (item) => item.path === `${sectionPath}/${folder.replace(/^\d+\./, "")}`,
99
- ),
100
- )
101
- .filter((item): item is ContentNavigationItem => item !== undefined);
102
- }
103
- } else {
104
- result = localeResult;
105
- }
106
- return [
107
- section.key,
108
- foldNonRouteCategories(flattenNavigation(result), appConfig.nonRouteCategories ?? {}),
109
- ] as const;
110
- }),
111
- );
112
- return Object.fromEntries(results) as Record<string, ContentNavigationItem[]>;
113
- },
114
- {
115
- watch: [locale],
116
- },
117
- );
118
-
119
- const flatNavigation = computed(() =>
120
- navigation.value ? Object.values(navigation.value).flat() : [],
121
- );
122
-
123
- provide("navigation", navigation);
124
- </script>
125
-
126
- <template>
127
- <UApp :locale="nuxtUiLocales[locale as keyof typeof nuxtUiLocales]">
128
- <NuxtLoadingIndicator color="var(--ui-primary)" />
129
-
130
- <NuxtLayout>
131
- <NuxtPage />
132
- </NuxtLayout>
133
-
134
- <ClientOnly>
135
- <AppSearch :navigation="flatNavigation" />
136
- </ClientOnly>
137
- </UApp>
138
- </template>