upcontent 0.0.0-stage → 0.1.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 (70) hide show
  1. package/.github/workflows/ci.yml +61 -0
  2. package/.github/workflows/deploy.yml +59 -0
  3. package/.github/workflows/reusable-pages.yml +74 -0
  4. package/.upcontent/config.json +82 -0
  5. package/.upcontent/favicon.svg +4 -0
  6. package/.upcontent/logo.svg +6 -0
  7. package/.upcontent/portal.css +139 -0
  8. package/Makefile +29 -0
  9. package/README.md +114 -2
  10. package/SHOWCASE.mdx +163 -0
  11. package/astro.config.mjs +125 -0
  12. package/customization/config-json.md +92 -0
  13. package/customization/content.md +48 -0
  14. package/customization/environment.md +43 -0
  15. package/customization/index.md +33 -0
  16. package/customization/navigation.md +57 -0
  17. package/customization/site-identity.md +43 -0
  18. package/customization/theme.md +47 -0
  19. package/deployment/github-pages.md +46 -0
  20. package/deployment/index.md +24 -0
  21. package/deployment/static-hosts.md +34 -0
  22. package/getting-started/consumer-repository.md +156 -0
  23. package/getting-started/first-build.md +78 -0
  24. package/guides/authoring-content.md +105 -0
  25. package/guides/validate-your-site.md +53 -0
  26. package/package.json +33 -3
  27. package/scripts/upcontent-cli.mjs +113 -0
  28. package/scripts/verify-external-build.mjs +24 -0
  29. package/src/components/MermaidLoader.astro +290 -0
  30. package/src/components/PaletteShowcase.astro +122 -0
  31. package/src/components/StructuredDataCopy.astro +22 -0
  32. package/src/content/__mocks__/astro-content.ts +7 -0
  33. package/src/content/__mocks__/astro-loaders.ts +3 -0
  34. package/src/content.config.test.ts +163 -0
  35. package/src/content.config.ts +90 -0
  36. package/src/lib/content-blocklist.test.ts +47 -0
  37. package/src/lib/content-blocklist.ts +55 -0
  38. package/src/lib/doc-links.test.ts +62 -0
  39. package/src/lib/doc-links.ts +31 -0
  40. package/src/lib/mermaid-render.test.ts +42 -0
  41. package/src/lib/mermaid-render.ts +24 -0
  42. package/src/lib/portal-config.test.ts +143 -0
  43. package/src/lib/portal-config.ts +176 -0
  44. package/src/lib/product-identity.ts +2 -0
  45. package/src/lib/rehype-callouts.test.ts +69 -0
  46. package/src/lib/rehype-callouts.ts +61 -0
  47. package/src/lib/remark-strip-duplicate-title.test.ts +73 -0
  48. package/src/lib/remark-strip-duplicate-title.ts +37 -0
  49. package/src/lib/remark-structured-data-preview.test.ts +104 -0
  50. package/src/lib/remark-structured-data-preview.ts +66 -0
  51. package/src/lib/remark-wiki-links.test.ts +102 -0
  52. package/src/lib/remark-wiki-links.ts +110 -0
  53. package/src/lib/sidebar.test.ts +142 -0
  54. package/src/lib/sidebar.ts +113 -0
  55. package/src/lib/structured-data-tree.test.ts +75 -0
  56. package/src/lib/structured-data-tree.ts +55 -0
  57. package/src/overrides/Footer.astro +114 -0
  58. package/src/overrides/Head.astro +20 -0
  59. package/src/pages/index.astro +20 -0
  60. package/src/styles/callouts.css +29 -0
  61. package/src/styles/structured-data-preview.css +95 -0
  62. package/test-fixtures/external-consumer/.upcontent/config.json +26 -0
  63. package/test-fixtures/external-consumer/.upcontent/favicon.svg +4 -0
  64. package/test-fixtures/external-consumer/.upcontent/logo.svg +4 -0
  65. package/test-fixtures/external-consumer/.upcontent/theme.css +4 -0
  66. package/test-fixtures/external-consumer/.upcontent-renderer/README.md +3 -0
  67. package/test-fixtures/external-consumer/README.md +6 -0
  68. package/test-fixtures/external-consumer/forbidden.md +5 -0
  69. package/tsconfig.json +7 -0
  70. package/vitest.config.ts +18 -0
package/SHOWCASE.mdx ADDED
@@ -0,0 +1,163 @@
1
+ ---
2
+ heading: Capability showcase
3
+ description: One practical page showing what a consumer repository can render, configure, and publish with Upcontent.
4
+ sidebar:
5
+ order: 1
6
+ ---
7
+
8
+ import PaletteShowcase from './src/components/PaletteShowcase.astro';
9
+
10
+ Upcontent turns Markdown into a searchable static documentation site. Configure the consumer repository, add the content formats your team needs, and publish the generated `dist/` directory to a static host.
11
+
12
+ ## Rendered content
13
+
14
+ The portal accepts ordinary Markdown and adds a small set of useful content features.
15
+
16
+ ### Callouts
17
+
18
+ :::tip[Use callouts for decisions]
19
+ Keep callouts short. Use them for a warning, a recommendation, or a next action that should not disappear in the surrounding text.
20
+ :::
21
+
22
+ :::caution
23
+ Static output is public wherever the selected host is public. A private source repository does not automatically make the published site private.
24
+ :::
25
+
26
+ ### Code and diagrams
27
+
28
+ ```ts
29
+ export function buildPortal(contentPath: string) {
30
+ return `make build CONTENT_PATH=${contentPath}`
31
+ }
32
+ ```
33
+
34
+ ```mermaid
35
+ flowchart LR
36
+ Content[Consumer repository] --> Config[.upcontent/config.json]
37
+ Config --> Build[Static build]
38
+ Content --> Build
39
+ Build --> Output[Searchable portal]
40
+ ```
41
+
42
+ Mermaid diagrams support theme synchronization, fullscreen, zoom, pan, source copying, and an invalid-source fallback.
43
+
44
+ ### Structured examples
45
+
46
+ JSON and YAML blocks remain readable as collapsible data:
47
+
48
+ ```json
49
+ {
50
+ "portal": "static",
51
+ "search": "pagefind",
52
+ "themes": ["light", "dark"]
53
+ }
54
+ ```
55
+
56
+ ```yaml
57
+ contentPath: ./docs
58
+ publish: github-pages
59
+ sourceOfTruth: consumer-repository
60
+ ```
61
+
62
+ CSV becomes a table when the content is tabular:
63
+
64
+ ```csv
65
+ capability,status
66
+ custom-css,available
67
+ blocklist,available
68
+ pagefind,available
69
+ ```
70
+
71
+ ### Links and navigation
72
+
73
+ Regular Markdown links are best for stable routes, such as [site validation](../guides/validate-your-site/). Wiki links are useful when the source repository refers to files by name, such as `[[README]]`. Missing wiki-link targets fail the build instead of producing broken navigation.
74
+
75
+ ## Consumer customization
76
+
77
+ The consumer changes identity and presentation in `.upcontent/`, without editing the portal renderer:
78
+
79
+ ```text
80
+ .upcontent/
81
+ ├── config.json
82
+ ├── favicon.svg
83
+ ├── logo.svg
84
+ └── theme.css
85
+ ```
86
+
87
+ The configuration controls:
88
+
89
+ - Site title, description, URL, logo, and favicon
90
+ - Repository links and local custom CSS
91
+ - Social links, table of contents, pagination, last-updated metadata, and code styling
92
+ - Sidebar roots, labels, and content blocklists
93
+ - The frontmatter field used as a page title
94
+
95
+ Example identity configuration:
96
+
97
+ ```json
98
+ {
99
+ "site": {
100
+ "title": "Engineering Docs",
101
+ "description": "Documentation for the engineering team.",
102
+ "logo": {
103
+ "src": ".upcontent/logo.svg",
104
+ "alt": "Engineering Docs"
105
+ },
106
+ "favicon": ".upcontent/favicon.svg"
107
+ },
108
+ "repo": {
109
+ "url": "https://github.com/acme/engineering-docs"
110
+ },
111
+ "theme": {
112
+ "customCss": [".upcontent/theme.css"]
113
+ }
114
+ }
115
+ ```
116
+
117
+ The [configuration reference](../customization/config-json/) explains every supported field and its fallback behavior.
118
+
119
+ ### Palette variations
120
+
121
+ The same Starlight surface can carry different consumer identities through tokens and custom CSS:
122
+
123
+ <PaletteShowcase />
124
+
125
+ These are examples for a consumer repository, not a runtime theme picker that every published portal needs to expose.
126
+
127
+ ## Content boundaries
128
+
129
+ The build excludes protected internal paths and consumer blocklists before parsing. This keeps excluded files out of both the content collection and the sidebar.
130
+
131
+ ```json
132
+ {
133
+ "navigation": {
134
+ "blocklist": {
135
+ "exact": ["notes.md"],
136
+ "prefixes": ["drafts/", "internal/"]
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ Broken wiki links and traversal attempts fail with the source file and invalid reference. The same pipeline also works when `CONTENT_PATH` points to a separate consumer repository.
143
+
144
+ ## Validation and publishing
145
+
146
+ The repeatable local contract is:
147
+
148
+ ```sh
149
+ pnpm test
150
+ pnpm check
151
+ make build CONTENT_PATH=.
152
+ make check-external
153
+ ```
154
+
155
+ The reference CI workflow validates the site and an external consumer fixture. The deployment workflow publishes `dist/` to a static host such as GitHub Pages.
156
+
157
+ ## Explore the product
158
+
159
+ - [Your first build](../getting-started/first-build/)
160
+ - [Set up a consumer repository](../getting-started/consumer-repository/)
161
+ - [Authoring content](../guides/authoring-content/)
162
+ - [Validate your site](../guides/validate-your-site/)
163
+ - [Deployment](../deployment/)
@@ -0,0 +1,125 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { cpSync, existsSync, mkdirSync, statSync } from 'node:fs'
3
+ import { basename, resolve, sep } from 'node:path'
4
+ import { unified } from '@astrojs/markdown-remark'
5
+ import starlight from '@astrojs/starlight'
6
+ import { defineConfig } from 'astro/config'
7
+ import { visit } from 'unist-util-visit'
8
+ import { rehypeCallouts } from './src/lib/rehype-callouts.ts'
9
+ import { remarkStripDuplicateTitle } from './src/lib/remark-strip-duplicate-title.ts'
10
+ import { remarkStructuredDataPreview } from './src/lib/remark-structured-data-preview.ts'
11
+ import { remarkWikiLinks } from './src/lib/remark-wiki-links.ts'
12
+ import { getPortalConfig } from './src/lib/portal-config.ts'
13
+ import { buildSidebar } from './src/lib/sidebar.ts'
14
+ import { PRODUCT_NAME, PRODUCT_TAGLINE } from './src/lib/product-identity.ts'
15
+
16
+ const docsRoot = fileURLToPath(new URL('./src/content/docs', import.meta.url))
17
+ const portalConfig = getPortalConfig()
18
+
19
+ function resolvePortalAsset(assetPath) {
20
+ if (!assetPath || assetPath.startsWith('http')) return assetPath
21
+ if (assetPath.startsWith('/')) return assetPath
22
+
23
+ const source = resolve(docsRoot, assetPath)
24
+ if (!source.startsWith(`${resolve(docsRoot)}${sep}`) || !existsSync(source) || !statSync(source).isFile()) {
25
+ console.warn(`[${PRODUCT_NAME}] Portal asset not found: ${source}`)
26
+ return undefined
27
+ }
28
+
29
+ const targetDir = resolve(process.cwd(), 'public/upcontent-assets')
30
+ mkdirSync(targetDir, { recursive: true })
31
+ const targetName = basename(source)
32
+ cpSync(source, resolve(targetDir, targetName))
33
+ return `/upcontent-assets/${targetName}`
34
+ }
35
+
36
+ function resolvePortalLogo(logo) {
37
+ if (!logo) return undefined
38
+ if (logo.src.startsWith('http') || logo.src.startsWith('/')) return logo
39
+ const source = resolve(docsRoot, logo.src)
40
+ if (!source.startsWith(`${resolve(docsRoot)}${sep}`) || !existsSync(source) || !statSync(source).isFile()) {
41
+ console.warn(`[${PRODUCT_NAME}] Portal logo not found: ${source}`)
42
+ return undefined
43
+ }
44
+ const targetDir = resolve(process.cwd(), 'src/assets')
45
+ mkdirSync(targetDir, { recursive: true })
46
+ const target = resolve(targetDir, 'consumer-logo.svg')
47
+ cpSync(source, target)
48
+ return { ...logo, src: './src/assets/consumer-logo.svg' }
49
+ }
50
+
51
+ const consumerCss = (portalConfig.theme?.customCss ?? [])
52
+ .map(cssPath => resolve(docsRoot, cssPath))
53
+ .filter(cssPath => {
54
+ if (existsSync(cssPath)) return true
55
+ console.warn(`[${PRODUCT_NAME}] Custom CSS file not found: ${cssPath}`)
56
+ return false
57
+ })
58
+
59
+ const customCss = ['./src/styles/callouts.css', './src/styles/structured-data-preview.css', ...consumerCss]
60
+ const starlightOptions = portalConfig.starlight ?? {}
61
+
62
+ // Remark plugin: converts ```mermaid blocks to <div class="mermaid"> BEFORE Shiki runs
63
+ function remarkMermaid() {
64
+ return (tree) => {
65
+ visit(tree, 'code', (node, index, parent) => {
66
+ if (node.lang !== 'mermaid') return
67
+ // <br/> inside a div becomes a DOM element before Mermaid parses the text, breaking the parser
68
+ const safe = node.value.replace(/<br\s*\/?>/gi, ' ')
69
+ parent.children.splice(index, 1, {
70
+ type: 'html',
71
+ value: `<div class="mermaid">${safe}</div>`,
72
+ })
73
+ return index + 1
74
+ })
75
+ }
76
+ }
77
+
78
+ // Rehype plugin: strip .md suffix from internal hrefs so links resolve correctly
79
+ function rehypeStripMdLinks() {
80
+ return (tree) => {
81
+ visit(tree, 'element', (node) => {
82
+ if (node.tagName !== 'a') return
83
+ const href = node.properties?.href
84
+ if (typeof href !== 'string') return
85
+ if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('#')) return
86
+ if (href.endsWith('.md')) node.properties.href = href.slice(0, -3)
87
+ })
88
+ }
89
+ }
90
+
91
+ export default defineConfig({
92
+ output: 'static',
93
+ site: process.env.SITE_URL || portalConfig.site?.url || undefined,
94
+ base: process.env.BASE_PATH || undefined,
95
+ integrations: [
96
+ starlight({
97
+ title: portalConfig.site?.title ?? PRODUCT_NAME,
98
+ description: portalConfig.site?.description ?? PRODUCT_TAGLINE,
99
+ logo: resolvePortalLogo(portalConfig.site?.logo),
100
+ favicon: resolvePortalAsset(portalConfig.site?.favicon),
101
+ components: {
102
+ Head: './src/overrides/Head.astro',
103
+ Footer: './src/overrides/Footer.astro',
104
+ },
105
+ customCss,
106
+ social: starlightOptions.social,
107
+ tableOfContents: starlightOptions.tableOfContents,
108
+ lastUpdated: starlightOptions.lastUpdated,
109
+ pagination: starlightOptions.pagination,
110
+ expressiveCode: starlightOptions.expressiveCode,
111
+ sidebar: buildSidebar(docsRoot),
112
+ }),
113
+ ],
114
+ markdown: {
115
+ processor: unified({
116
+ remarkPlugins: [
117
+ remarkStripDuplicateTitle,
118
+ [remarkWikiLinks, { contentRoot: docsRoot, failOnBrokenLinks: true }],
119
+ remarkMermaid,
120
+ remarkStructuredDataPreview,
121
+ ],
122
+ rehypePlugins: [rehypeCallouts, rehypeStripMdLinks],
123
+ }),
124
+ },
125
+ })
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: JSON configuration reference
3
+ description: Complete reference for the consumer-owned .upcontent/config.json file.
4
+ sidebar:
5
+ order: 6
6
+ ---
7
+
8
+ The `.upcontent/config.json` file is optional. An omitted field uses the portal or Starlight default.
9
+
10
+ ## Start with the minimum
11
+
12
+ You only need a title and repository link to get started. Add a logo and custom CSS when you are ready to make the portal yours:
13
+
14
+ ```json
15
+ {
16
+ "site": {
17
+ "title": "Engineering Docs",
18
+ "description": "Documentation for the engineering team."
19
+ },
20
+ "repo": {
21
+ "url": "https://github.com/acme/engineering-docs"
22
+ }
23
+ }
24
+ ```
25
+
26
+ Relative asset paths resolve from the consumer repository. Absolute URLs are also supported for externally hosted assets.
27
+
28
+ ## Complete example
29
+
30
+ The optional fields below cover navigation, Starlight presentation, and content conventions. You can add them one group at a time.
31
+
32
+ <details>
33
+ <summary>Show the complete configuration</summary>
34
+
35
+ ```json
36
+ {
37
+ "site": {
38
+ "title": "Engineering Docs",
39
+ "description": "Documentation for the engineering team.",
40
+ "url": "https://docs.example.com",
41
+ "logo": {
42
+ "src": ".upcontent/logo.svg",
43
+ "alt": "Engineering Docs",
44
+ "replacesTitle": false
45
+ },
46
+ "favicon": ".upcontent/favicon.svg"
47
+ },
48
+ "repo": {
49
+ "url": "https://github.com/acme/engineering-docs"
50
+ },
51
+ "theme": {
52
+ "customCss": [".upcontent/theme.css"]
53
+ },
54
+ "starlight": {
55
+ "social": [
56
+ { "icon": "github", "label": "GitHub", "href": "https://github.com/acme/engineering-docs" }
57
+ ],
58
+ "tableOfContents": { "minHeadingLevel": 2, "maxHeadingLevel": 3 },
59
+ "lastUpdated": true,
60
+ "pagination": true,
61
+ "expressiveCode": {
62
+ "styleOverrides": { "borderRadius": "0.6rem" }
63
+ }
64
+ },
65
+ "navigation": {
66
+ "roots": ["README.md", "guides"],
67
+ "labelOverrides": { "api": "API reference" },
68
+ "blocklist": {
69
+ "exact": ["notes.md"],
70
+ "prefixes": ["drafts/"]
71
+ }
72
+ },
73
+ "content": {
74
+ "titleField": "title"
75
+ }
76
+ }
77
+ ```
78
+
79
+ </details>
80
+
81
+ ## Field groups
82
+
83
+ | Group | Controls |
84
+ | --- | --- |
85
+ | `site` | Name, description, URL, logo, and favicon. |
86
+ | `repo` | Source repository links. |
87
+ | `theme` | Consumer-owned local CSS. |
88
+ | `starlight` | Safe layout, social, table of contents, pagination, and code options. |
89
+ | `navigation` | Sidebar roots, labels, and blocklists. |
90
+ | `content` | Frontmatter title field selection. |
91
+
92
+ Invalid curated values are ignored or fall back safely. Heading levels must be integers from 1 through 6, and the minimum cannot exceed the maximum.
@@ -0,0 +1,48 @@
1
+ ---
2
+ title: Content behavior
3
+ description: Configure page titles, metadata, and the Markdown features available to authors.
4
+ sidebar:
5
+ order: 5
6
+ ---
7
+
8
+ Upcontent accepts Markdown and MDX files from the consumer repository. Starlight provides the page layout and frontmatter schema; Upcontent adds title fallback, wiki links, callouts, Mermaid, and structured previews.
9
+
10
+ ## Choose a title field
11
+
12
+ If an existing repository uses a field such as `heading` instead of `title`, configure it once:
13
+
14
+ ```json
15
+ {
16
+ "content": {
17
+ "titleField": "heading"
18
+ }
19
+ }
20
+ ```
21
+
22
+ When the configured field is missing, the portal falls back to the filename. A file named `first-build.md` becomes `First Build`.
23
+
24
+ ## Page metadata
25
+
26
+ Use frontmatter at the top of a page:
27
+
28
+ ```md
29
+ ---
30
+ heading: Configure a consumer repository
31
+ sidebar:
32
+ order: 2
33
+ ---
34
+ ```
35
+
36
+ Use `##` for body sections because the page title is rendered as the top-level heading.
37
+
38
+ ## Supported content features
39
+
40
+ - Obsidian-style callouts for notes, tips, cautions, and dangers
41
+ - Mermaid diagrams with theme-aware rendering and interaction controls
42
+ - JSON and YAML data previews
43
+ - CSV tables
44
+ - `[[wiki links]]` with build-time target validation
45
+ - Syntax-highlighted code blocks
46
+ - Standard Markdown links, tables, lists, and images
47
+
48
+ See [Authoring content](../guides/authoring-content/) for writing rules and the [capability showcase](../showcase/) for rendered examples.
@@ -0,0 +1,43 @@
1
+ ---
2
+ title: Environment variables
3
+ description: Configure repository links and hosting paths at build time.
4
+ sidebar:
5
+ order: 7
6
+ ---
7
+
8
+ The portal is static, but a few values belong to the build environment rather than consumer content.
9
+
10
+ ## Supported variables
11
+
12
+ | Variable | Purpose | Example |
13
+ | --- | --- | --- |
14
+ | `CONTENT_PATH` | Filesystem path passed to `make dev` or `make build`. | `./` |
15
+ | `REPO_URL` | Overrides the configured source repository URL for generated GitHub links. | `https://github.com/acme/docs` |
16
+ | `BASE_PATH` | URL prefix for a project site hosted below the domain root. | `/engineering-docs` |
17
+ | `SITE_URL` | Canonical site origin used by Astro integrations such as the sitemap. | `https://docs.example.com` |
18
+
19
+ ## Local development
20
+
21
+ Pass the content path to Make:
22
+
23
+ ```sh
24
+ make dev CONTENT_PATH=.
25
+ ```
26
+
27
+ `CONTENT_PATH` is a Make variable, not a value read from `.upcontent/config.json`.
28
+
29
+ ## Static hosting
30
+
31
+ For GitHub Pages project sites, set a base path and site URL in the deployment workflow:
32
+
33
+ ```yaml
34
+ env:
35
+ BASE_PATH: /engineering-docs
36
+ SITE_URL: https://acme.github.io/engineering-docs
37
+ ```
38
+
39
+ For a custom domain or a host serving the site at `/`, leave `BASE_PATH` empty and set `SITE_URL` to the canonical origin.
40
+
41
+ ## Precedence
42
+
43
+ `REPO_URL` takes precedence over `repo.url` for generated source links. `BASE_PATH` and `SITE_URL` are build inputs; they do not change the consumer configuration file.
@@ -0,0 +1,33 @@
1
+ ---
2
+ title: Customization
3
+ description: Configure the portal from the consumer repository without editing the renderer.
4
+ sidebar:
5
+ order: 1
6
+ ---
7
+
8
+ Customization belongs to the consumer repository. The renderer stays shared; the consumer decides what the site is called, how it looks, what appears in navigation, and where it is published.
9
+
10
+ ## Choose a path
11
+
12
+ - [Site identity](site-identity/): title, description, logo, favicon, and repository links.
13
+ - [Theme and CSS](theme/): colors, typography, spacing, and dark mode using Starlight tokens.
14
+ - [Navigation](navigation/): sidebar roots, labels, and content blocklists.
15
+ - [Content behavior](content/): title fields, page metadata, and supported rendered content.
16
+ - [JSON configuration](config-json/): the complete `.upcontent/config.json` guide.
17
+ - [Environment variables](environment/): build-time values for repository URLs and hosting paths.
18
+
19
+ The [configuration reference](config-json/) is the source of truth for supported fields. The [capability showcase](../showcase/) demonstrates the result in one page.
20
+
21
+ ## The customization boundary
22
+
23
+ Consumer-owned files normally live here:
24
+
25
+ ```text
26
+ .upcontent/
27
+ ├── config.json
28
+ ├── favicon.svg
29
+ ├── logo.svg
30
+ └── theme.css
31
+ ```
32
+
33
+ If a customization requires changing `astro.config.mjs`, it is not part of the normal consumer configuration surface.
@@ -0,0 +1,57 @@
1
+ ---
2
+ title: Navigation
3
+ description: Shape the sidebar and keep internal material out of the published portal.
4
+ sidebar:
5
+ order: 4
6
+ ---
7
+
8
+ The sidebar is generated from the consumer file structure. Use configuration when the default filesystem order is not the right experience for readers.
9
+
10
+ ## Select top-level roots
11
+
12
+ ```json
13
+ {
14
+ "navigation": {
15
+ "roots": [
16
+ "README.md",
17
+ "getting-started",
18
+ "guides",
19
+ "reference"
20
+ ]
21
+ }
22
+ }
23
+ ```
24
+
25
+ `roots` limits the files and folders shown at the top level. It does not move files or change their URLs.
26
+
27
+ ## Rename generated labels
28
+
29
+ ```json
30
+ {
31
+ "navigation": {
32
+ "labelOverrides": {
33
+ "api": "API reference",
34
+ "runbooks": "Runbooks"
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ Keys are matched case-insensitively. Use labels that describe the reader's destination, not the team's internal shorthand.
41
+
42
+ ## Exclude content before parsing
43
+
44
+ ```json
45
+ {
46
+ "navigation": {
47
+ "blocklist": {
48
+ "exact": ["notes.md"],
49
+ "prefixes": ["drafts/", "internal/"]
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ Blocklisted paths are excluded from the content collection and sidebar before Markdown is parsed. The portal also protects internal paths such as `.upcontent/`, `.github/`, `src/`, and `node_modules/`.
56
+
57
+ Blocklisting is not access control. Do not put secrets in a repository that will be published to a public host.
@@ -0,0 +1,43 @@
1
+ ---
2
+ title: Site identity
3
+ description: Give a consumer portal its own name, assets, and repository links.
4
+ sidebar:
5
+ order: 2
6
+ ---
7
+
8
+ The consumer controls the visible identity of the published portal.
9
+
10
+ ## Configuration
11
+
12
+ ```json
13
+ {
14
+ "site": {
15
+ "title": "Engineering Docs",
16
+ "description": "Documentation for the engineering team.",
17
+ "url": "https://docs.example.com",
18
+ "logo": {
19
+ "src": ".upcontent/logo.svg",
20
+ "alt": "Engineering Docs",
21
+ "replacesTitle": false
22
+ },
23
+ "favicon": ".upcontent/favicon.svg"
24
+ },
25
+ "repo": {
26
+ "url": "https://github.com/acme/engineering-docs"
27
+ }
28
+ }
29
+ ```
30
+
31
+ ## Logo behavior
32
+
33
+ Use a compact logo mark when the header should display the site title beside it. Set `replacesTitle` to `true` when the logo already contains the full wordmark.
34
+
35
+ Always provide useful alternative text when the logo communicates identity. Use an empty `alt` when the adjacent visible site title already provides the accessible name.
36
+
37
+ Relative asset paths resolve from the consumer repository. Logo and favicon assets are copied into the static output during the build.
38
+
39
+ ## Repository links
40
+
41
+ `repo.url` powers links that let readers view or edit the source document on GitHub. Set it to the repository containing the content, not the repository containing the shared portal renderer.
42
+
43
+ If the content repository is private, confirm that the links are appropriate for the readers who will receive the published site.
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: Theme and CSS
3
+ description: Customize color, typography, and spacing with the official Starlight CSS seam.
4
+ sidebar:
5
+ order: 3
6
+ ---
7
+
8
+ Use `theme.customCss` to refine the Starlight surface without replacing its layout or accessibility behavior.
9
+
10
+ ## Load a consumer stylesheet
11
+
12
+ ```json
13
+ {
14
+ "theme": {
15
+ "customCss": [".upcontent/theme.css"]
16
+ }
17
+ }
18
+ ```
19
+
20
+ The path is relative to the consumer repository. CSS is bundled during the build, so keep the stylesheet in the content repository rather than depending on a remote stylesheet.
21
+
22
+ ## Use Starlight tokens
23
+
24
+ ```css
25
+ :root {
26
+ --sl-color-accent: #0f766e;
27
+ --sl-color-accent-high: #115e59;
28
+ --sl-font: 'Poppins', sans-serif;
29
+ }
30
+
31
+ :root[data-theme='dark'] {
32
+ --sl-color-accent: #5eead4;
33
+ --sl-color-accent-high: #99f6e4;
34
+ }
35
+ ```
36
+
37
+ Tokens keep custom colors aligned with callouts, links, code blocks, and theme switching.
38
+
39
+ ## Keep the interface coherent
40
+
41
+ - Define both light and dark values for strong colors.
42
+ - Prefer tokens over hard-coded colors in component selectors.
43
+ - Keep body text readable before adjusting decorative styles.
44
+ - Use one display or body family consistently instead of styling every section differently.
45
+ - Check keyboard focus and reduced-motion behavior after adding transitions.
46
+
47
+ The site's stylesheet is a working example of this seam.