@zebradil/docs-kit 0.0.0-stage → 0.10.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.
- package/LICENSE +21 -0
- package/README.md +136 -2
- package/package.json +41 -4
- package/packages/docs-kit/Cascade.astro +205 -0
- package/packages/docs-kit/Diff.astro +134 -0
- package/packages/docs-kit/Landing.astro +372 -0
- package/packages/docs-kit/Tree.astro +139 -0
- package/packages/docs-kit/content.mjs +5 -0
- package/packages/docs-kit/index.mjs +77 -0
- package/packages/docs-kit/kit.test.mjs +45 -0
- package/packages/docs-kit/layout.css +5 -0
- package/packages/docs-kit/llms.txt.mjs +31 -0
- package/packages/docs-kit/page.md.mjs +42 -0
- package/packages/docs-kit/text.mjs +6 -0
- package/packages/docs-kit/themes/default.css +12 -0
- package/packages/docs-kit/themes/mono.css +30 -0
- package/packages/docs-kit/themes/porridge.css +42 -0
- package/packages/docs-kit/themes/sieve.css +49 -0
- package/packages/docs-kit/themes/vortex.css +45 -0
- package/refgen/help2md.mjs +85 -0
- package/schema/site.schema.json +537 -0
- package/schema/validate.mjs +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 German Lashevich
|
|
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/README.md
CHANGED
|
@@ -1,3 +1,137 @@
|
|
|
1
|
-
#
|
|
1
|
+
# docs-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared kit for generating browsable documentation and a landing page for small OSS projects.
|
|
4
|
+
|
|
5
|
+
- One content contract (`docs/site.yaml` + Diátaxis page tree) across all projects.
|
|
6
|
+
- One layout, swappable designs (theme = CSS tokens).
|
|
7
|
+
- Reference pages generated from code (CLI `--help`, config schema), never handwritten.
|
|
8
|
+
- Reusable GitHub Actions workflow: generate, build, deploy to GitHub Pages.
|
|
9
|
+
- Agent skills (Claude Code plugin): [`docs-audit`](skills/docs-audit/SKILL.md), [`docs-bootstrap`](skills/docs-bootstrap/SKILL.md), [`docs-write`](skills/docs-write/SKILL.md).
|
|
10
|
+
|
|
11
|
+
## Claude Code plugin
|
|
12
|
+
|
|
13
|
+
This repository is a Claude Code plugin marketplace with one plugin, `docs-kit`, holding the three skills. In a Claude Code session:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
/plugin marketplace add Zebradil/docs-kit
|
|
17
|
+
/plugin install docs-kit@docs-kit
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The shell equivalents are `claude plugin marketplace add Zebradil/docs-kit` and `claude plugin install docs-kit@docs-kit`. The skills run as `/docs-kit:docs-audit` and so on, or when a request matches their description.
|
|
21
|
+
|
|
22
|
+
Recommended flow in a consuming project:
|
|
23
|
+
|
|
24
|
+
1. `docs-audit` writes the gap report `docs/.audit.md`; it never edits docs.
|
|
25
|
+
2. Review the report: reorder, drop, or fix gaps before any prose lands.
|
|
26
|
+
3. `docs-bootstrap` sets up `docs/`, `site.yaml`, the workflow, and Renovate, then opens a PR.
|
|
27
|
+
4. `docs-write` closes one gap per run with one PR. Repeat, re-auditing when the code moves.
|
|
28
|
+
|
|
29
|
+
The plugin carries no `version`, so its version is the commit it was installed from. Third-party marketplaces do not auto-update by default; pull the latest `main` with `claude plugin marketplace update docs-kit && claude plugin update docs-kit@docs-kit`.
|
|
30
|
+
|
|
31
|
+
## Content contract
|
|
32
|
+
|
|
33
|
+
Every consuming project has a `docs/site.yaml` validated by [`schema/site.schema.json`](schema/site.schema.json); see [`examples/site.yaml`](examples/site.yaml). Validate one locally:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npm ci
|
|
37
|
+
node schema/validate.mjs path/to/docs/site.yaml
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Using the kit
|
|
41
|
+
|
|
42
|
+
The repository root is the npm package [`@zebradil/docs-kit`](https://www.npmjs.com/package/@zebradil/docs-kit), published from each release tag ([ADR 0007](docs/adr/0007-publish-to-npm.md)). A consuming project's `docs/` needs three files besides `site.yaml` and its pages under `src/content/docs/`:
|
|
43
|
+
|
|
44
|
+
<!-- x-release-please-start-version -->
|
|
45
|
+
```jsonc
|
|
46
|
+
// package.json
|
|
47
|
+
{ "private": true, "type": "module", "dependencies": { "@zebradil/docs-kit": "0.10.0" } }
|
|
48
|
+
```
|
|
49
|
+
<!-- x-release-please-end -->
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
// astro.config.mjs
|
|
53
|
+
import docsKit from '@zebradil/docs-kit';
|
|
54
|
+
|
|
55
|
+
export default docsKit({ site: 'site.yaml' });
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
// src/content.config.mjs
|
|
60
|
+
export { collections } from '@zebradil/docs-kit/content';
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Build with `npm ci && npx astro build` from `docs/`. The site is served at `https://<owner>.github.io/<repo>/`, derived from `repo:` in `site.yaml`; with `domain:` set, it is served at `https://<domain>/`. [`packages/docs-kit/fixture/`](packages/docs-kit/fixture/) is a complete example.
|
|
64
|
+
|
|
65
|
+
To count visitors, set `analytics:` in `site.yaml`; the script is loaded on every page with the given `data-*` attributes:
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
analytics:
|
|
69
|
+
script: https://stats.example.dev/app.js
|
|
70
|
+
attributes:
|
|
71
|
+
data-website-id: 00000000-0000-0000-0000-000000000000
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For coding agents and LLMs, every site serves [`/llms.txt`](https://llmstxt.org), an index of all pages, and `/llms-full.txt`, all pages in one file. Each page's Markdown source is also served at its URL with `.md` appended (`/guides/configure/` → `/guides/configure.md`), with relative links made absolute. GitHub Pages cannot vary a response on the `Accept` header, so the `.md` URLs are the only Markdown route.
|
|
75
|
+
|
|
76
|
+
### Publishing with GitHub Actions
|
|
77
|
+
|
|
78
|
+
[`.github/workflows/docs.yml`](.github/workflows/docs.yml) is a reusable workflow. A consuming project calls it from `.github/workflows/docs.yml`, pinned to the same tag as the kit in `docs/package.json`:
|
|
79
|
+
|
|
80
|
+
<!-- x-release-please-start-version -->
|
|
81
|
+
```yaml
|
|
82
|
+
name: docs
|
|
83
|
+
on:
|
|
84
|
+
push: { branches: [main] }
|
|
85
|
+
pull_request:
|
|
86
|
+
permissions: { contents: read, pages: write, id-token: write }
|
|
87
|
+
jobs:
|
|
88
|
+
docs:
|
|
89
|
+
uses: Zebradil/docs-kit/.github/workflows/docs.yml@v0.10.0
|
|
90
|
+
```
|
|
91
|
+
<!-- x-release-please-end -->
|
|
92
|
+
|
|
93
|
+
Keep `uses:` on its own indented line: Renovate's github-actions manager finds refs line by line and misses one inside a flow mapping.
|
|
94
|
+
|
|
95
|
+
The caller must grant `pages: write` and `id-token: write`: a called workflow can only narrow the caller's token permissions, never widen them, and the deploy job needs both.
|
|
96
|
+
|
|
97
|
+
On every run it installs `docs/` with `npm ci`, builds the CLI with `reference.cli.build` (run from the repository root) when set, regenerates the CLI reference with `help2md` into `docs/src/content/docs/reference/cli`, and builds the site with `npx astro build` (which validates `site.yaml`). On a pull request it fails if the regenerated reference differs from the committed one and prints the diff. On a push to the default branch it deploys `docs/dist` to GitHub Pages.
|
|
98
|
+
|
|
99
|
+
Inputs, all optional:
|
|
100
|
+
|
|
101
|
+
| Input | Default | Use |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `directory` | `docs` | Docs directory, relative to the repository root. |
|
|
104
|
+
| `nix` | `false` | Install Nix first, for `build: nix build` and friends. |
|
|
105
|
+
| `deploy` | `true` | Deploy on pushes to the default branch. |
|
|
106
|
+
|
|
107
|
+
Runs on `ubuntu-latest`, which already has Rust (cargo, rustup) and Go (default version linked into `/usr/bin`; a newer `go` line in `go.mod` makes Go download that toolchain itself); see the [runner image tool list](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md).
|
|
108
|
+
|
|
109
|
+
One manual step per repository, by its owner: enable Pages with GitHub Actions as the source, in Settings → Pages → Build and deployment → Source: GitHub Actions, or:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
gh api --method POST repos/<owner>/<repo>/pages -f build_type=workflow
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Until then the deploy job fails. This repository builds [the fixture](packages/docs-kit/fixture/) through the workflow ([`pages.yml`](.github/workflows/pages.yml)) and deploys it only when the repository variable `PAGES_ENABLED` is `true`; set it after enabling Pages (`gh variable set PAGES_ENABLED --body true`).
|
|
116
|
+
|
|
117
|
+
Terms used across the kit are defined in [`CONTEXT.md`](CONTEXT.md).
|
|
118
|
+
|
|
119
|
+
Status: pre-release. Kit, workflow and skills are in place; no `v*` tag yet, first pilot pending. See the design map issue (#1).
|
|
120
|
+
|
|
121
|
+
## refgen/help2md
|
|
122
|
+
|
|
123
|
+
Generates CLI reference pages from `--help` output (clap and cobra), one Starlight page per command:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
node refgen/help2md.mjs --bin target/release/kasha --out docs/src/content/docs/reference/cli
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The kit ships it as the `help2md` binary, so a consuming project runs the version it pins, from `docs/`:
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
npx --no-install help2md --bin ../target/release/kasha --out src/content/docs/reference/cli
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`--no-install` matters: without it, a missing kit makes `npx` fetch an unrelated `help2md` package from the npm registry.
|
|
136
|
+
|
|
137
|
+
Pages are named after the command path (`kasha.md`, `kasha-config-set.md`). `help` and cobra's default `completion` are skipped. Tests: `node --test`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zebradil/docs-kit",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"type": "module",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Zebradil/docs-kit.git"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./packages/docs-kit/index.mjs",
|
|
14
|
+
"./content": "./packages/docs-kit/content.mjs"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"help2md": "refgen/help2md.mjs"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"packages/docs-kit",
|
|
21
|
+
"!packages/docs-kit/fixture",
|
|
22
|
+
"schema/site.schema.json",
|
|
23
|
+
"schema/validate.mjs",
|
|
24
|
+
"refgen/help2md.mjs"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "node --test",
|
|
28
|
+
"validate": "node schema/validate.mjs"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@astrojs/starlight": "0.42.10",
|
|
32
|
+
"@fontsource-variable/fraunces": "5.3.0",
|
|
33
|
+
"@fontsource-variable/instrument-sans": "5.3.0",
|
|
34
|
+
"@fontsource-variable/jetbrains-mono": "5.3.0",
|
|
35
|
+
"@fontsource-variable/schibsted-grotesk": "5.3.0",
|
|
36
|
+
"@fontsource/ibm-plex-mono": "5.3.0",
|
|
37
|
+
"@fontsource/outfit": "5.3.0",
|
|
38
|
+
"ajv": "8.20.0",
|
|
39
|
+
"astro": "7.4.7",
|
|
40
|
+
"yaml": "2.9.1"
|
|
41
|
+
},
|
|
42
|
+
"version": "0.10.0"
|
|
43
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The cascade panel: one request falling through ordered tiers of backends, one tab per scenario.
|
|
3
|
+
import { TabItem, Tabs } from '@astrojs/starlight/components';
|
|
4
|
+
import site from 'virtual:docs-kit/site';
|
|
5
|
+
import { escape, inline } from './text.mjs';
|
|
6
|
+
|
|
7
|
+
const cascade = site.cascade;
|
|
8
|
+
const glyph = { hit: '●', miss: '×', down: '○', idle: '·' };
|
|
9
|
+
const word = { hit: 'hit', miss: 'miss', down: 'down', idle: 'not asked' };
|
|
10
|
+
const state = (scenario, id) => scenario.states[id] ?? { state: 'idle' };
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<section class="kit-cascade" aria-labelledby="kit-cascade-title">
|
|
14
|
+
<div class="kit-cascade-head">
|
|
15
|
+
<h2 id="kit-cascade-title">{cascade.title ?? 'How a request falls through'}</h2>
|
|
16
|
+
{cascade.request && <code>{cascade.request}</code>}
|
|
17
|
+
</div>
|
|
18
|
+
{
|
|
19
|
+
cascade.hub && (
|
|
20
|
+
<div class="kit-cascade-hub">
|
|
21
|
+
<span class="kit-cascade-hub-name">{cascade.hub.name}</span>
|
|
22
|
+
{cascade.hub.body && <span class="kit-cascade-hub-body" set:html={inline(cascade.hub.body)} />}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
<Tabs>
|
|
27
|
+
{
|
|
28
|
+
cascade.scenarios.map((scenario) => (
|
|
29
|
+
<TabItem label={scenario.label}>
|
|
30
|
+
<ol class="kit-cascade-tiers">
|
|
31
|
+
{cascade.tiers.map((tier) => (
|
|
32
|
+
<li class="kit-cascade-tier">
|
|
33
|
+
<div class="kit-cascade-tier-name">
|
|
34
|
+
{tier.label && <span>{tier.label}</span>}
|
|
35
|
+
<strong>{tier.name}</strong>
|
|
36
|
+
</div>
|
|
37
|
+
<ul>
|
|
38
|
+
{tier.backends.map((b) => {
|
|
39
|
+
const s = state(scenario, b.id);
|
|
40
|
+
return (
|
|
41
|
+
<li class="kit-cascade-backend" data-state={s.state}>
|
|
42
|
+
<code>{b.name}</code>
|
|
43
|
+
<span>
|
|
44
|
+
{glyph[s.state]} {word[s.state]}
|
|
45
|
+
{s.text && ` · ${s.text}`}
|
|
46
|
+
</span>
|
|
47
|
+
</li>
|
|
48
|
+
);
|
|
49
|
+
})}
|
|
50
|
+
</ul>
|
|
51
|
+
</li>
|
|
52
|
+
))}
|
|
53
|
+
</ol>
|
|
54
|
+
{scenario.note && <p class="kit-cascade-note" set:html={inline(scenario.note)} />}
|
|
55
|
+
</TabItem>
|
|
56
|
+
))
|
|
57
|
+
}
|
|
58
|
+
</Tabs>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<style is:global>
|
|
62
|
+
.kit-cascade {
|
|
63
|
+
border: 1px solid var(--sl-color-hairline-light);
|
|
64
|
+
border-radius: 1.5rem;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
}
|
|
67
|
+
.kit-cascade-head {
|
|
68
|
+
display: flex;
|
|
69
|
+
flex-wrap: wrap;
|
|
70
|
+
justify-content: space-between;
|
|
71
|
+
align-items: baseline;
|
|
72
|
+
gap: 0.5rem 1.5rem;
|
|
73
|
+
padding: 1.125rem 1.5rem;
|
|
74
|
+
border-bottom: 1px solid var(--sl-color-hairline-light);
|
|
75
|
+
}
|
|
76
|
+
.kit-landing .kit-cascade-head h2 {
|
|
77
|
+
margin: 0;
|
|
78
|
+
font-family: var(--sl-font-mono);
|
|
79
|
+
font-size: var(--sl-text-xs);
|
|
80
|
+
font-weight: 400;
|
|
81
|
+
letter-spacing: 0.08em;
|
|
82
|
+
text-transform: uppercase;
|
|
83
|
+
color: var(--sl-color-gray-3);
|
|
84
|
+
}
|
|
85
|
+
.kit-cascade-head code {
|
|
86
|
+
padding: 0;
|
|
87
|
+
background: none;
|
|
88
|
+
font-size: var(--sl-text-sm);
|
|
89
|
+
color: var(--sl-color-gray-2);
|
|
90
|
+
}
|
|
91
|
+
.kit-cascade-hub {
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-wrap: wrap;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: 0.75rem 1.25rem;
|
|
96
|
+
padding: 1.25rem 1.5rem;
|
|
97
|
+
border-bottom: 1px solid var(--sl-color-hairline-light);
|
|
98
|
+
background-image: radial-gradient(circle, var(--sl-color-gray-5) 1.4px, transparent 1.6px);
|
|
99
|
+
background-size: 12px 12px;
|
|
100
|
+
}
|
|
101
|
+
.kit-cascade-hub-name {
|
|
102
|
+
padding: 0.5rem 0.875rem;
|
|
103
|
+
border-radius: 0.625rem;
|
|
104
|
+
background: var(--sl-color-white);
|
|
105
|
+
color: var(--sl-color-black);
|
|
106
|
+
font-weight: 700;
|
|
107
|
+
}
|
|
108
|
+
.kit-cascade-hub-body {
|
|
109
|
+
padding: 0.125rem 0.375rem;
|
|
110
|
+
border-radius: 0.375rem;
|
|
111
|
+
background: var(--sl-color-black);
|
|
112
|
+
color: var(--sl-color-gray-2);
|
|
113
|
+
}
|
|
114
|
+
/* Starlight's tab strip, inset to line up with the panel's rows. */
|
|
115
|
+
.kit-cascade starlight-tabs {
|
|
116
|
+
display: block;
|
|
117
|
+
}
|
|
118
|
+
.kit-cascade starlight-tabs .tablist-wrapper {
|
|
119
|
+
padding: 0.75rem 1.5rem 0;
|
|
120
|
+
}
|
|
121
|
+
.kit-cascade [role='tabpanel'] {
|
|
122
|
+
margin: 0;
|
|
123
|
+
}
|
|
124
|
+
.kit-cascade-tiers,
|
|
125
|
+
.kit-cascade-tier ul {
|
|
126
|
+
margin: 0;
|
|
127
|
+
padding: 0;
|
|
128
|
+
list-style: none;
|
|
129
|
+
}
|
|
130
|
+
.kit-cascade-tier {
|
|
131
|
+
display: flex;
|
|
132
|
+
flex-wrap: wrap;
|
|
133
|
+
margin: 0;
|
|
134
|
+
border-bottom: 1px dashed var(--sl-color-hairline-light);
|
|
135
|
+
}
|
|
136
|
+
.kit-cascade-tier:last-child {
|
|
137
|
+
border-bottom: 0;
|
|
138
|
+
}
|
|
139
|
+
.kit-cascade-tier-name {
|
|
140
|
+
flex: 1 1 10rem;
|
|
141
|
+
display: flex;
|
|
142
|
+
flex-direction: column;
|
|
143
|
+
gap: 0.25rem;
|
|
144
|
+
padding: 1.375rem 1.5rem;
|
|
145
|
+
}
|
|
146
|
+
.kit-cascade-tier-name span {
|
|
147
|
+
font-family: var(--sl-font-mono);
|
|
148
|
+
font-size: var(--sl-text-xs);
|
|
149
|
+
color: var(--sl-color-gray-3);
|
|
150
|
+
}
|
|
151
|
+
.kit-cascade-tier-name strong {
|
|
152
|
+
font-size: var(--sl-text-lg);
|
|
153
|
+
color: var(--sl-color-white);
|
|
154
|
+
}
|
|
155
|
+
.kit-cascade-tier ul {
|
|
156
|
+
flex: 999 1 30rem;
|
|
157
|
+
display: grid;
|
|
158
|
+
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
|
159
|
+
gap: 0.75rem;
|
|
160
|
+
padding: 1.125rem 1.5rem;
|
|
161
|
+
}
|
|
162
|
+
.kit-cascade-backend {
|
|
163
|
+
display: flex;
|
|
164
|
+
flex-direction: column;
|
|
165
|
+
gap: 0.375rem;
|
|
166
|
+
margin: 0;
|
|
167
|
+
padding: 0.875rem 1rem;
|
|
168
|
+
border: 1px solid var(--sl-color-hairline-light);
|
|
169
|
+
border-radius: 0.875rem;
|
|
170
|
+
font-family: var(--sl-font-mono);
|
|
171
|
+
font-size: var(--sl-text-xs);
|
|
172
|
+
color: var(--sl-color-gray-3);
|
|
173
|
+
}
|
|
174
|
+
.kit-cascade-backend code {
|
|
175
|
+
padding: 0;
|
|
176
|
+
background: none;
|
|
177
|
+
font-size: var(--sl-text-sm);
|
|
178
|
+
overflow-wrap: anywhere;
|
|
179
|
+
color: var(--sl-color-gray-3);
|
|
180
|
+
}
|
|
181
|
+
.kit-cascade-backend[data-state='hit'] {
|
|
182
|
+
border: 1.5px solid var(--sl-color-accent);
|
|
183
|
+
background: var(--sl-color-accent-low);
|
|
184
|
+
color: var(--sl-color-text-accent);
|
|
185
|
+
}
|
|
186
|
+
.kit-cascade-backend[data-state='hit'] code {
|
|
187
|
+
color: var(--sl-color-white);
|
|
188
|
+
}
|
|
189
|
+
.kit-cascade-backend[data-state='down'] {
|
|
190
|
+
border: 1.5px dashed var(--sl-color-orange);
|
|
191
|
+
color: var(--sl-color-orange-high);
|
|
192
|
+
}
|
|
193
|
+
.kit-cascade-backend:is([data-state='down'], [data-state='miss']) code {
|
|
194
|
+
color: var(--sl-color-gray-2);
|
|
195
|
+
}
|
|
196
|
+
.kit-cascade-note {
|
|
197
|
+
margin: 0;
|
|
198
|
+
padding: 1rem 1.5rem;
|
|
199
|
+
border-top: 1px solid var(--sl-color-hairline-light);
|
|
200
|
+
background: var(--sl-color-bg-nav);
|
|
201
|
+
font-size: var(--sl-text-sm);
|
|
202
|
+
line-height: 1.5;
|
|
203
|
+
color: var(--sl-color-gray-2);
|
|
204
|
+
}
|
|
205
|
+
</style>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The diff panel: a change on one side, the files it changes in each target on the other, one tab per target.
|
|
3
|
+
import { Code, TabItem, Tabs } from '@astrojs/starlight/components';
|
|
4
|
+
import site from 'virtual:docs-kit/site';
|
|
5
|
+
import { inline } from './text.mjs';
|
|
6
|
+
|
|
7
|
+
const diff = site.diff;
|
|
8
|
+
const count = (files, sign) => files.flatMap((f) => f.diff.split('\n')).filter((l) => l.startsWith(sign)).length;
|
|
9
|
+
const summary = (files) =>
|
|
10
|
+
files.length
|
|
11
|
+
? `${files.length} ${files.length === 1 ? 'file' : 'files'} changed, +${count(files, '+')} −${count(files, '-')}`
|
|
12
|
+
: 'No changes';
|
|
13
|
+
// Expressive Code's own `diff` language leaves a diff with hunk headers unmarked, so the kit turns the
|
|
14
|
+
// +/- column into ins/del line markers, marks the hunk headers, and highlights the file in its own language.
|
|
15
|
+
const file = ({ path, diff }) => {
|
|
16
|
+
const marks = { ins: [], del: [], mark: [] };
|
|
17
|
+
const lines = diff.replace(/\n$/, '').split('\n').map((line, i) => {
|
|
18
|
+
if (line.startsWith('@@')) return marks.mark.push(i + 1) && line;
|
|
19
|
+
if (line[0] === '+') marks.ins.push(i + 1);
|
|
20
|
+
if (line[0] === '-') marks.del.push(i + 1);
|
|
21
|
+
return line.slice(1);
|
|
22
|
+
});
|
|
23
|
+
const meta = Object.entries(marks).filter(([, n]) => n.length).map(([k, n]) => `${k}={${n}}`);
|
|
24
|
+
return { code: lines.join('\n'), lang: path.split('.').pop().replace(/^yml$/, 'yaml'), meta: [`title="${path}"`, ...meta].join(' ') };
|
|
25
|
+
};
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<section class="kit-diff" aria-labelledby="kit-diff-title">
|
|
29
|
+
<div class="kit-diff-head">
|
|
30
|
+
<h2 id="kit-diff-title">{diff.title ?? 'What a change looks like'}</h2>
|
|
31
|
+
{diff.intro && <p set:html={inline(diff.intro)} />}
|
|
32
|
+
</div>
|
|
33
|
+
<div class="kit-diff-body">
|
|
34
|
+
{
|
|
35
|
+
diff.change && (
|
|
36
|
+
<div class="kit-diff-change">
|
|
37
|
+
<p class="kit-diff-label">
|
|
38
|
+
{diff.change.label ?? 'The change'} <span>{summary(diff.change.files)}</span>
|
|
39
|
+
</p>
|
|
40
|
+
{diff.change.files.map((f) => (
|
|
41
|
+
<Code {...file(f)} />
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
<div class="kit-diff-result">
|
|
47
|
+
<p class="kit-diff-label">{diff.label ?? 'What it changes'}</p>
|
|
48
|
+
<Tabs>
|
|
49
|
+
{
|
|
50
|
+
diff.tabs.map((t) => (
|
|
51
|
+
<TabItem label={t.label}>
|
|
52
|
+
<p class="kit-diff-summary">{summary(t.files)}</p>
|
|
53
|
+
{t.files.map((f) => (
|
|
54
|
+
<Code {...file(f)} />
|
|
55
|
+
))}
|
|
56
|
+
</TabItem>
|
|
57
|
+
))
|
|
58
|
+
}
|
|
59
|
+
</Tabs>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</section>
|
|
63
|
+
|
|
64
|
+
<style is:global>
|
|
65
|
+
.kit-landing .kit-diff-head {
|
|
66
|
+
max-width: 44rem;
|
|
67
|
+
}
|
|
68
|
+
.kit-diff-head p {
|
|
69
|
+
margin: 0;
|
|
70
|
+
line-height: 1.55;
|
|
71
|
+
color: var(--sl-color-gray-2);
|
|
72
|
+
}
|
|
73
|
+
.kit-diff-body {
|
|
74
|
+
display: grid;
|
|
75
|
+
grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
|
|
76
|
+
gap: 1.5rem;
|
|
77
|
+
margin-top: 2rem;
|
|
78
|
+
}
|
|
79
|
+
@media (max-width: 60rem) {
|
|
80
|
+
.kit-diff-body {
|
|
81
|
+
grid-template-columns: minmax(0, 1fr);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
.kit-diff-change,
|
|
85
|
+
.kit-diff-result {
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
gap: 1rem;
|
|
89
|
+
min-width: 0;
|
|
90
|
+
padding: 1.25rem;
|
|
91
|
+
border: 1px solid var(--sl-color-hairline-light);
|
|
92
|
+
border-radius: 1rem;
|
|
93
|
+
background: var(--sl-color-bg-nav);
|
|
94
|
+
}
|
|
95
|
+
.kit-diff-result {
|
|
96
|
+
border-color: var(--sl-color-accent);
|
|
97
|
+
}
|
|
98
|
+
.kit-diff-label {
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-wrap: wrap;
|
|
101
|
+
justify-content: space-between;
|
|
102
|
+
gap: 0.25rem 1rem;
|
|
103
|
+
margin: 0;
|
|
104
|
+
font-family: var(--sl-font-mono);
|
|
105
|
+
font-size: var(--sl-text-xs);
|
|
106
|
+
letter-spacing: 0.06em;
|
|
107
|
+
text-transform: uppercase;
|
|
108
|
+
color: var(--sl-color-gray-3);
|
|
109
|
+
}
|
|
110
|
+
.kit-diff-label span,
|
|
111
|
+
.kit-diff-summary {
|
|
112
|
+
margin: 0;
|
|
113
|
+
font-family: var(--sl-font-mono);
|
|
114
|
+
font-size: var(--sl-text-xs);
|
|
115
|
+
letter-spacing: 0;
|
|
116
|
+
text-transform: none;
|
|
117
|
+
color: var(--sl-color-gray-2);
|
|
118
|
+
}
|
|
119
|
+
.kit-diff starlight-tabs {
|
|
120
|
+
display: block;
|
|
121
|
+
}
|
|
122
|
+
.kit-diff [role='tabpanel'] {
|
|
123
|
+
display: flex;
|
|
124
|
+
flex-direction: column;
|
|
125
|
+
gap: 1rem;
|
|
126
|
+
margin-top: 1rem;
|
|
127
|
+
}
|
|
128
|
+
.kit-diff [role='tabpanel'][hidden] {
|
|
129
|
+
display: none;
|
|
130
|
+
}
|
|
131
|
+
.kit-diff .expressive-code {
|
|
132
|
+
margin: 0;
|
|
133
|
+
}
|
|
134
|
+
</style>
|