hd-garden 0.7.0 → 0.8.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 (110) hide show
  1. package/.agents/skills/garden-ui/SKILL.md +26 -19
  2. package/README.en.md +57 -0
  3. package/README.md +31 -46
  4. package/app/blog/[...path]/page.tsx +9 -1
  5. package/app/columns/[column]/[...path]/page.tsx +10 -12
  6. package/app/components/ArticlePager.tsx +32 -0
  7. package/app/components/Comments.tsx +2 -1
  8. package/app/components/FeedMore.tsx +70 -0
  9. package/app/components/GardenDocsNav.tsx +25 -20
  10. package/app/components/HandbookLangRedirect.tsx +30 -0
  11. package/app/components/HandbookLangSwitch.tsx +49 -0
  12. package/app/components/HandbookView.tsx +187 -0
  13. package/app/components/MarkdownArticle.tsx +11 -5
  14. package/app/components/MarkdownImage.tsx +6 -4
  15. package/app/components/SiteChrome.tsx +11 -2
  16. package/app/components/SiteFooter.tsx +5 -3
  17. package/app/components/SiteHeader.tsx +27 -23
  18. package/app/components/TableOfContents.tsx +15 -9
  19. package/app/components/ThoughtFeed.tsx +41 -0
  20. package/app/components/UiLang.tsx +18 -0
  21. package/app/components/Webmentions.tsx +3 -2
  22. package/app/components/garden/FilePreview.tsx +13 -11
  23. package/app/components/garden/GardenBlock.tsx +3 -1
  24. package/app/components/garden/GardenFile.tsx +9 -7
  25. package/app/components/garden/blocks.tsx +34 -24
  26. package/app/components/garden/chrome.tsx +20 -13
  27. package/app/garden/[...slug]/page.tsx +10 -130
  28. package/app/garden/en/[...slug]/page.tsx +28 -0
  29. package/app/garden/en/page.tsx +16 -0
  30. package/app/garden/page.tsx +7 -74
  31. package/app/garden-modules.d.ts +3 -0
  32. package/app/layout.tsx +2 -4
  33. package/app/lib/content.ts +12 -2
  34. package/app/lib/garden-docs-meta.ts +60 -5
  35. package/app/lib/handbook-path.ts +11 -0
  36. package/app/lib/i18n.ts +222 -0
  37. package/app/lib/page-alternates.ts +2 -2
  38. package/app/not-found.tsx +5 -3
  39. package/app/site.config.ts +1 -1
  40. package/app/styles/a2.css +101 -32
  41. package/app/styles/shell.css +6 -0
  42. package/app/thoughts/page/[n]/page.tsx +44 -0
  43. package/app/thoughts/page.tsx +2 -6
  44. package/bin/garden.mjs +4 -0
  45. package/docs/handbook/ai-writing.md +99 -0
  46. package/docs/handbook/components/card.md +2 -0
  47. package/docs/handbook/components/chart.md +2 -0
  48. package/docs/handbook/components/compare.md +10 -12
  49. package/docs/handbook/components/figure.md +2 -0
  50. package/docs/handbook/components/file.md +2 -0
  51. package/docs/handbook/components/html.md +2 -0
  52. package/docs/handbook/components/interactive.md +2 -0
  53. package/docs/handbook/components/stat.md +2 -0
  54. package/docs/handbook/components/timeline.md +2 -0
  55. package/docs/handbook/components.md +1 -1
  56. package/docs/handbook/config.md +2 -2
  57. package/docs/handbook/deploy.md +1 -1
  58. package/docs/handbook/design.md +3 -3
  59. package/docs/handbook/en/ai-writing.md +99 -0
  60. package/docs/handbook/en/components/card.md +46 -0
  61. package/docs/handbook/en/components/chart.md +54 -0
  62. package/docs/handbook/en/components/compare.md +38 -0
  63. package/docs/handbook/en/components/figure.md +72 -0
  64. package/docs/handbook/en/components/file/checklist.md +17 -0
  65. package/docs/handbook/en/components/file.md +34 -0
  66. package/docs/handbook/en/components/html.md +69 -0
  67. package/docs/handbook/en/components/interactive.md +67 -0
  68. package/docs/handbook/en/components/stat.md +57 -0
  69. package/docs/handbook/en/components/timeline.md +52 -0
  70. package/docs/handbook/en/components.md +30 -0
  71. package/docs/handbook/en/config.md +125 -0
  72. package/docs/handbook/en/deploy.md +273 -0
  73. package/docs/handbook/en/design.md +81 -0
  74. package/docs/handbook/en/index.md +192 -0
  75. package/docs/handbook/en/start.md +92 -0
  76. package/docs/handbook/en/workflow.md +81 -0
  77. package/docs/handbook/en/writing.md +156 -0
  78. package/docs/handbook/index.md +98 -3
  79. package/docs/handbook/writing.md +18 -10
  80. package/package.json +1 -1
  81. package/scripts/build-content.mjs +23 -3
  82. package/scripts/handbook-facts.mjs +34 -0
  83. package/scripts/html-lang.mjs +36 -0
  84. package/scripts/static-404.mjs +25 -0
  85. package/scripts/ui-cli.mjs +14 -8
  86. package/templates/site/site.config.yaml +2 -0
  87. package/ui/core/apply.mjs +22 -4
  88. package/ui/core/cli-locale.mjs +67 -0
  89. package/ui/core/fence.mjs +18 -3
  90. package/ui/core/fix-prompt.mjs +18 -15
  91. package/ui/core/garden-fence.mjs +64 -0
  92. package/ui/core/scan.mjs +19 -3
  93. package/ui/garden/adjacent.mjs +21 -0
  94. package/ui/garden/check.mjs +6 -0
  95. package/ui/garden/html-lang.mjs +16 -0
  96. package/ui/garden/paging.mjs +30 -0
  97. package/ui/preview/dist/garden-ui.css +9 -9
  98. package/ui/preview/dist/garden-ui.esm.js +169 -169
  99. package/ui/preview/dist/garden-ui.iife.js +169 -169
  100. package/ui/react/components/chart.tsx +7 -4
  101. package/ui/react/components/display.tsx +6 -3
  102. package/ui/react/components/inputs.tsx +6 -3
  103. package/ui/react/components/layout.tsx +6 -4
  104. package/ui/react/components/media.tsx +7 -4
  105. package/ui/react/components/sandbox.tsx +3 -1
  106. package/ui/react/index.tsx +11 -7
  107. package/ui/react/lang.tsx +85 -0
  108. package/ui/react/runtime.tsx +3 -1
  109. package/ui/react/ui.css +9 -9
  110. package/worker/index.ts +8 -1
@@ -0,0 +1,273 @@
1
+ ---
2
+ title: Deploy to GitHub Pages
3
+ navTitle: Deploy to GitHub Pages
4
+ description: Configure a GitHub Actions workflow that supports either a single public repository or a private source repo paired with a public Pages repo.
5
+ section: getting-started
6
+ order: 4
7
+ ---
8
+
9
+ `npx hd-garden init` does not create `.github/workflows/pages.yml` automatically. Add this file to your site repository when you are ready to publish to GitHub Pages. The same workflow supports both a **single public repository** and a **private source repository + public Pages repository**, switching automatically based on whether the `PAGES_REPOSITORY` variable is set.
10
+
11
+ The workflow below matches Garden's own `.github/workflows/pages.yml` with two adjustments for a site repo: it omits `paths-ignore`, `npm run lint`, and `npm test` (which only apply to the engine repo), and its `publish-repository` job installs dependencies before running `npx garden pages:prepare static-site pages-repository` (the same `scripts/prepare-pages-output.mjs` script Garden calls directly). Commit `package-lock.json` after your first `npm install` so `npm ci` can run in CI.
12
+
13
+ The `build` job runs only when the repository is named `<owner>.github.io`, or when the `SITE_URL` or `PAGES_REPOSITORY` variable is set.
14
+
15
+ ## 1. Full `.github/workflows/pages.yml` Workflow
16
+
17
+ Create `.github/workflows/pages.yml` in your site repository:
18
+
19
+ ```yaml
20
+ name: Verify and publish blog
21
+
22
+ on:
23
+ push:
24
+ branches: [main]
25
+ workflow_dispatch:
26
+
27
+ permissions:
28
+ contents: read
29
+
30
+ concurrency:
31
+ group: publish-blog
32
+ cancel-in-progress: true
33
+
34
+ jobs:
35
+ build:
36
+ if: github.event.repository.name == format('{0}.github.io', github.repository_owner) || vars.SITE_URL != '' || vars.PAGES_REPOSITORY != ''
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - uses: actions/checkout@v7
40
+
41
+ - uses: actions/setup-node@v7
42
+ with:
43
+ node-version: 22
44
+ cache: npm
45
+
46
+ - run: npm ci
47
+
48
+ - name: Resolve the public URL
49
+ env:
50
+ SITE_URL_OVERRIDE: ${{ vars.SITE_URL }}
51
+ ADMIN_URL_OVERRIDE: ${{ vars.ADMIN_URL }}
52
+ GISCUS_REPO: ${{ vars.GISCUS_REPO }}
53
+ GISCUS_REPO_ID: ${{ vars.GISCUS_REPO_ID }}
54
+ GISCUS_CATEGORY: ${{ vars.GISCUS_CATEGORY }}
55
+ GISCUS_CATEGORY_ID: ${{ vars.GISCUS_CATEGORY_ID }}
56
+ GISCUS_DISABLED: ${{ vars.GISCUS_DISABLED }}
57
+ WEBMENTION_IO_DOMAIN: ${{ vars.WEBMENTION_IO_DOMAIN }}
58
+ ANALYTICS_DOMAIN: ${{ vars.ANALYTICS_DOMAIN }}
59
+ ANALYTICS_SCRIPT: ${{ vars.ANALYTICS_SCRIPT }}
60
+ run: |
61
+ owner="$GITHUB_REPOSITORY_OWNER"
62
+ default_url="https://$owner.github.io"
63
+ giscus_ready=1
64
+ if [ -z "$GISCUS_REPO" ] || [ "$GISCUS_REPO" = "owner/repository" ]; then giscus_ready=0; fi
65
+ if [ -z "$GISCUS_REPO_ID" ] || [ "$GISCUS_REPO_ID" = "R_xxxxxxxxxx" ]; then giscus_ready=0; fi
66
+ if [ -z "$GISCUS_CATEGORY" ]; then giscus_ready=0; fi
67
+ if [ -z "$GISCUS_CATEGORY_ID" ] || [ "$GISCUS_CATEGORY_ID" = "DIC_xxxxxxxxxx" ]; then giscus_ready=0; fi
68
+ if [ "$giscus_ready" != 1 ]; then
69
+ if [ "$GISCUS_DISABLED" = "1" ]; then
70
+ GISCUS_REPO=
71
+ GISCUS_REPO_ID=
72
+ GISCUS_CATEGORY=
73
+ GISCUS_CATEGORY_ID=
74
+ echo "GISCUS_DISABLED=1,本次构建不开放评论。"
75
+ else
76
+ echo "评论还没有配置,构建已停止。"
77
+ echo "1. 准备一个公开 GitHub 仓库,并开启 Discussions。"
78
+ echo "2. 打开 https://giscus.app/zh-CN ,安装 Giscus App,选中该仓库和分类。"
79
+ echo "3. 在 Settings → Secrets and variables → Actions → Variables 写入:"
80
+ echo " GISCUS_REPO、GISCUS_REPO_ID、GISCUS_CATEGORY、GISCUS_CATEGORY_ID"
81
+ echo "确定不开放评论时,把 GISCUS_DISABLED 设为 1。留空和示例值 owner/repository 都不会通过。"
82
+ exit 1
83
+ fi
84
+ fi
85
+ {
86
+ echo "SITE_URL=${SITE_URL_OVERRIDE:-$default_url}"
87
+ echo "NEXT_PUBLIC_SITE_URL=${SITE_URL_OVERRIDE:-$default_url}"
88
+ echo "ADMIN_URL=$ADMIN_URL_OVERRIDE"
89
+ echo "GISCUS_REPO=$GISCUS_REPO"
90
+ echo "GISCUS_REPO_ID=$GISCUS_REPO_ID"
91
+ echo "GISCUS_CATEGORY=$GISCUS_CATEGORY"
92
+ echo "GISCUS_CATEGORY_ID=$GISCUS_CATEGORY_ID"
93
+ echo "NEXT_PUBLIC_GISCUS_REPO=$GISCUS_REPO"
94
+ echo "NEXT_PUBLIC_GISCUS_REPO_ID=$GISCUS_REPO_ID"
95
+ echo "NEXT_PUBLIC_GISCUS_CATEGORY=$GISCUS_CATEGORY"
96
+ echo "NEXT_PUBLIC_GISCUS_CATEGORY_ID=$GISCUS_CATEGORY_ID"
97
+ echo "WEBMENTION_IO_DOMAIN=$WEBMENTION_IO_DOMAIN"
98
+ echo "NEXT_PUBLIC_ANALYTICS_DOMAIN=$ANALYTICS_DOMAIN"
99
+ echo "NEXT_PUBLIC_ANALYTICS_SCRIPT=$ANALYTICS_SCRIPT"
100
+ echo "STATIC_EXPORT=1"
101
+ } >> "$GITHUB_ENV"
102
+
103
+ - name: Build the public site
104
+ env:
105
+ GITHUB_TOKEN: ${{ github.token }}
106
+ run: npm run build
107
+
108
+ - name: Validate the static output
109
+ run: npm run pages:validate
110
+
111
+ - name: Configure GitHub Pages
112
+ if: ${{ vars.PAGES_REPOSITORY == '' }}
113
+ uses: actions/configure-pages@v6
114
+
115
+ - name: Upload the GitHub Pages artifact
116
+ if: ${{ vars.PAGES_REPOSITORY == '' }}
117
+ uses: actions/upload-pages-artifact@v5
118
+ with:
119
+ path: dist/client
120
+
121
+ - name: Upload the public-repository artifact
122
+ if: ${{ vars.PAGES_REPOSITORY != '' }}
123
+ uses: actions/upload-artifact@v7
124
+ with:
125
+ name: static-site
126
+ path: dist/client
127
+ include-hidden-files: true
128
+ if-no-files-found: error
129
+ retention-days: 1
130
+
131
+ deploy-pages:
132
+ if: ${{ vars.PAGES_REPOSITORY == '' }}
133
+ needs: build
134
+ permissions:
135
+ pages: write
136
+ id-token: write
137
+ environment:
138
+ name: github-pages
139
+ url: ${{ steps.deployment.outputs.page_url }}
140
+ runs-on: ubuntu-latest
141
+ steps:
142
+ - name: Deploy the GitHub Pages artifact
143
+ id: deployment
144
+ uses: actions/deploy-pages@v5
145
+
146
+ publish-repository:
147
+ if: ${{ vars.PAGES_REPOSITORY != '' }}
148
+ needs: build
149
+ runs-on: ubuntu-latest
150
+ steps:
151
+ - uses: actions/checkout@v7
152
+
153
+ - uses: actions/setup-node@v7
154
+ with:
155
+ node-version: 22
156
+ cache: npm
157
+
158
+ - run: npm ci
159
+
160
+ - name: Check the split-repository configuration
161
+ env:
162
+ PAGES_DEPLOY_KEY: ${{ secrets.PAGES_DEPLOY_KEY }}
163
+ TARGET_REPOSITORY: ${{ vars.PAGES_REPOSITORY }}
164
+ run: |
165
+ test -n "$PAGES_DEPLOY_KEY" || { echo "缺少 PAGES_DEPLOY_KEY Secret"; exit 1; }
166
+ [[ "$TARGET_REPOSITORY" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] || {
167
+ echo "PAGES_REPOSITORY 格式无效"
168
+ exit 1
169
+ }
170
+
171
+ - uses: actions/download-artifact@v8
172
+ with:
173
+ name: static-site
174
+ path: static-site
175
+
176
+ - name: Check out the public output repository
177
+ uses: actions/checkout@v7
178
+ with:
179
+ repository: ${{ vars.PAGES_REPOSITORY }}
180
+ ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }}
181
+ path: pages-repository
182
+ fetch-depth: 1
183
+
184
+ - name: Start a clean rolling deployment
185
+ working-directory: pages-repository
186
+ env:
187
+ TARGET_REPOSITORY: ${{ vars.PAGES_REPOSITORY }}
188
+ run: |
189
+ expected_remote="git@github.com:$TARGET_REPOSITORY.git"
190
+ actual_remote="$(git remote get-url origin)"
191
+ test "$actual_remote" = "$expected_remote" || {
192
+ echo "拒绝发布到非目标仓库:$actual_remote"
193
+ exit 1
194
+ }
195
+ echo "PAGES_PREVIOUS_TREE=$(git rev-parse 'HEAD^{tree}')" >> "$GITHUB_ENV"
196
+ git checkout --orphan pages-deploy
197
+ git rm -q -r -f .
198
+
199
+ - name: Prepare generated-only repository contents
200
+ run: npx garden pages:prepare static-site pages-repository
201
+
202
+ - name: Publish the rolling deployment commit
203
+ working-directory: pages-repository
204
+ run: |
205
+ git config user.name "github-actions[bot]"
206
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
207
+ git add -A
208
+ new_tree="$(git write-tree)"
209
+ if [ "$new_tree" = "$PAGES_PREVIOUS_TREE" ]; then
210
+ echo "静态产物没有变化,无需发布"
211
+ exit 0
212
+ fi
213
+ git commit -m "Deploy ${GITHUB_SHA}"
214
+ git push --force origin HEAD:main
215
+ ```
216
+
217
+ ## 2. Setting Up Each Deployment Mode
218
+
219
+ ### Mode 1: Single Public Repository (`PAGES_REPOSITORY` Left Empty)
220
+
221
+ If your source repository is public, name it `<owner>.github.io`. If you use another repository name, attach a custom domain to Pages and set the `SITE_URL` variable to that domain, or the `build` job will not run (Garden does not support subpath deployments such as `/<repo>/`):
222
+
223
+ 1. Open **Settings → Pages** in the repository and set **Source** to **GitHub Actions**.
224
+ 2. In **Settings → Secrets and variables → Actions → Variables**, add `GISCUS_DISABLED=1` (or set the 4 `GISCUS_*` variables below).
225
+ 3. Push to `main`; the workflow runs `deploy-pages` to publish directly.
226
+
227
+ ### Mode 2: Private Source Repository + Public Pages Repository
228
+
229
+ To keep drafts (`draft: true`) and commit history private, store your source files in a private repository (such as `blog-source`) and push only the generated static files to a public `<owner>.github.io` repository:
230
+
231
+ 1. **Generate an SSH deploy key** locally:
232
+ ```bash
233
+ ssh-keygen -t ed25519 -C "garden-pages-deploy" -f ./pages_deploy_key
234
+ ```
235
+ 2. **Add the public key to the public repository**: Open **Settings → Deploy keys → Add deploy key** in `<owner>.github.io`, paste `pages_deploy_key.pub`, and check **Allow write access**.
236
+ 3. **Configure Pages on the public repository**: Open **Settings → Pages** in `<owner>.github.io`, set **Source** to **Deploy from a branch**, and select `main` with `/ (root)`.
237
+ 4. **Configure variables and secrets on the private repository**: Open **Settings → Secrets and variables → Actions** in the private source repo:
238
+ - Under **Variables**, add `PAGES_REPOSITORY` set to `<owner>/<owner>.github.io` (along with `GISCUS_DISABLED=1` or the 4 `GISCUS_*` variables).
239
+ - Under **Secrets**, add `PAGES_DEPLOY_KEY` with the full contents of the private key file `pages_deploy_key`.
240
+ 5. On every push to the private repo, the `publish-repository` job checks out the public repository using `PAGES_DEPLOY_KEY`, verifies that `PAGES_DEPLOY_KEY` is present, `PAGES_REPOSITORY` matches `owner/repo`, and the remote URL matches the target repository, then runs `npx garden pages:prepare static-site pages-repository` to write the static output, `.nojekyll`, and a short `README.md` before force-pushing a single orphan commit to `main`.
241
+
242
+ ## 3. Giscus Comments Configuration
243
+
244
+ Article comments use GitHub Discussions via [Giscus](https://giscus.app). The application code reads `GISCUS_REPO`, `GISCUS_REPO_ID`, `GISCUS_CATEGORY`, and `GISCUS_CATEGORY_ID` (and their `NEXT_PUBLIC_GISCUS_*` counterparts), while the `Resolve the public URL` step in `pages.yml` checks that all four values are present before building:
245
+
246
+ - **Disable comments**: Set `GISCUS_DISABLED=1` in GitHub Actions Variables; the workflow clears `GISCUS_*` and omits the comment section.
247
+ - **Enable comments**: Enable Discussions on a public repository, install the Giscus App, and copy the four values into GitHub Actions Variables.
248
+
249
+ > [!NOTE] Note
250
+ > `npx garden pages:validate` inspects the `dist/client` directory (verifying that `index.html` exists and that no symlinks, `.env` files, `node_modules`, `.git`, `app`, `content`, `.md`, `.map`, or `.ts` files leaked into the export, apart from `.md` attachments under `media/`). It does not check Giscus variables itself.
251
+
252
+ ## 4. Environment Variables Reference
253
+
254
+ All environment variables can be placed in a local `.env.local` file (never committed to Git) or in your deployment platform's Variables and Secrets:
255
+
256
+ | Variable | Purpose |
257
+ | :--- | :--- |
258
+ | `SITE_URL` / `NEXT_PUBLIC_SITE_URL` | Public root URL of the site (such as `https://example.com`), used for canonical URLs, RSS feeds, and `sitemap.xml` |
259
+ | `NEXT_PUBLIC_BASE_PATH` | Optional base path prefix (empty by default) |
260
+ | `STATIC_EXPORT` | Set to `1` to export static HTML and enable trailing slashes on internal links |
261
+ | `TRAILING_SLASH` | Set to `1` in server builds to enable trailing slashes on internal links (already on when `STATIC_EXPORT=1`) |
262
+ | `CONTENT_INCLUDE_DRAFTS` | Set to `1` to include `draft: true` drafts and future `publishAt` posts in the build |
263
+ | `GITHUB_TOKEN` | Fetches public GitHub profile data and pinned repositories at build time (pass `${{ github.token }}` in GitHub Actions) |
264
+ | `GISCUS_DISABLED` | Read by the `pages.yml` workflow script: set to `1` to build without the 4 `GISCUS_*` variables |
265
+ | `GISCUS_REPO` / `GISCUS_REPO_ID` | Public GitHub repository (`owner/repo`) and Repository ID for Giscus |
266
+ | `GISCUS_CATEGORY` / `GISCUS_CATEGORY_ID` | Discussions category name and Category ID for Giscus |
267
+ | `WEBMENTION_IO_DOMAIN` / `WEBMENTION_ENDPOINT` | Domain registered with Webmention.io (such as `example.com`) and custom endpoint override |
268
+ | `ADMIN_URL` | Public URL of a separately deployed `/admin` instance |
269
+ | `CONTENT_REPOSITORY` / `CONTENT_BRANCH` | Target GitHub repository (`owner/repo`) and branch (defaults to `main`) where `/admin` commits content |
270
+ | `CONTENT_GITHUB_TOKEN` | Fine-grained GitHub PAT used by `/admin` to commit thoughts and assets |
271
+ | `GITHUB_OAUTH_CLIENT_ID` / `GITHUB_OAUTH_CLIENT_SECRET` | Client ID and Secret of the GitHub OAuth App for `/admin` sign-in |
272
+ | `GITHUB_SESSION_SECRET` | Random secret string used to sign `/admin` session cookies |
273
+ | `ADMIN_GITHUB_LOGINS` | Comma-separated allowlist of GitHub usernames permitted to sign in to `/admin` |
@@ -0,0 +1,81 @@
1
+ ---
2
+ title: How the Reading Layout Works
3
+ navTitle: How the Reading Layout Works
4
+ description: Three-tier 740/1040/1340px adaptive widths, two-line title splitting, zero-CLS image sizing, dark-mode image backing, and warm paper tokens.
5
+ section: writing-and-reading
6
+ order: 11
7
+ ---
8
+
9
+ Garden automates several layout details for long-form technical reading. Blog posts, column entries, and this handbook share the same stylesheets and measurement scripts.
10
+
11
+ ## 1. Three-tier Adaptive Widths: `740px` / `1040px` / `1340px`
12
+
13
+ An article page consists of a centered reading column and a floating right-hand table of contents (`200px`). On load and window resize, the `ArticleMeasure` script measures wide figures and tables in the body and expands them symmetrically into the available horizontal margins:
14
+
15
+ | Element | Max Width | Expansion Rule and Boundaries |
16
+ | :--- | :--- | :--- |
17
+ | Paragraphs, lists, code blocks, built-in components | `740px` | Body text uses `17px` font size and `1.75` line height (or `1.6` on English handbook pages) and stays inside the reading column |
18
+ | Wide figures (`.fig`) | `1040px` | Retina screenshots default to half their pixel width (`naturalWidth / 2`); when wider than the column on viewports `>= 900px`, they extend up to `150px` on each side (keeping at least `24px` from the viewport edge, or staying between the sidebar and TOC on handbook pages) |
19
+ | Wide tables (`.table-wrap`) | `1340px` | Measured at `max-content` without wrapping; when wider than `740px` on viewports `>= 900px`, tables extend up to `300px` on each side |
20
+ | Floating table of contents (`.toc`) | `200px` | On standalone post pages, when a widened figure or table approaches the TOC, the TOC shifts right to clear it or hides temporarily if the viewport has no room, reappearing once scrolled past |
21
+
22
+ ## 2. Two-line Title Splitting and Font Scaling
23
+
24
+ The `ArticleTitle` component applies two rules to headings:
25
+
26
+ 1. **With a delimiter (` | ` with surrounding spaces)**: Splits the title into two lines at the first delimiter and computes the `--l` scaling variable from the wider line's character width (counting CJK characters as `1`, uppercase letters as `0.68`, other characters as `0.58`, and spaces as `0.3`) so the longer line fits without wrapping again. Screen readers still read the full title with the delimiter.
27
+ 2. **Without a delimiter**: Renders a single `<h1>` with CSS `text-wrap: balance`, never splitting automatically on colons or dashes and never rescaling the font size.
28
+
29
+ > [!NOTE] Note
30
+ > If a title includes a split delimiter with an empty left or right side, `npx garden check` emits a `title-split` warning requiring text on both sides of the delimiter.
31
+
32
+ ## 3. Build-time Image Dimensions and Lightbox Zoom
33
+
34
+ When `npx garden sync` runs, the build script reads the pixel width and height of every local image (PNG, JPEG, WebP, GIF, and SVG) and writes them into the `imageSizes` map in `.garden/generated-content.ts`:
35
+
36
+ ```garden figure
37
+ caption: Click the buttons above to inspect the three stages from build to browser render
38
+ svg: |
39
+ <svg viewBox="0 0 680 110" xmlns="http://www.w3.org/2000/svg" font-family="var(--sans)">
40
+ <rect data-p="sync" x="12" y="18" width="196" height="74" rx="10" fill="var(--card)" stroke="var(--rule-strong)" stroke-width="1.5"/>
41
+ <text data-p="sync" x="110" y="50" text-anchor="middle" font-size="14" font-weight="600" fill="var(--ink)">1. Build-time Sizing</text>
42
+ <text data-p="sync" x="110" y="72" text-anchor="middle" font-size="12" fill="var(--muted)">Emit width / height &amp; --wl</text>
43
+ <path d="M214 55 H236" stroke="var(--muted)" stroke-width="1.5"/>
44
+ <rect data-p="measure" x="242" y="18" width="196" height="74" rx="10" fill="var(--card)" stroke="var(--rule-strong)" stroke-width="1.5"/>
45
+ <text data-p="measure" x="340" y="50" text-anchor="middle" font-size="14" font-weight="600" fill="var(--ink)">2. Client Measurement</text>
46
+ <text data-p="measure" x="340" y="72" text-anchor="middle" font-size="12" fill="var(--muted)">Fit wide figures &amp; TOC</text>
47
+ <path d="M444 55 H466" stroke="var(--muted)" stroke-width="1.5"/>
48
+ <rect data-p="dark" x="472" y="18" width="196" height="74" rx="10" fill="var(--card)" stroke="var(--rule-strong)" stroke-width="1.5"/>
49
+ <text data-p="dark" x="570" y="50" text-anchor="middle" font-size="14" font-weight="600" fill="var(--ink)">3. Dark Backing &amp; Zoom</text>
50
+ <text data-p="dark" x="570" y="72" text-anchor="middle" font-size="12" fill="var(--muted)">Pad #faf9f5 · Click to zoom</text>
51
+ </svg>
52
+ parts:
53
+ - id: sync
54
+ label: 1. Build-time Sizing
55
+ text: Server-rendered HTML already carries the image's `width`, `height`, `data-nw`, `data-bw`, and initial `--wl` / `--wr`, preventing layout shift before the image finishes downloading.
56
+ - id: measure
57
+ label: 2. Client Measurement
58
+ text: After load, `ArticleMeasure` recomputes the expansion margins for the current viewport width and checks on scroll whether a wide figure overlaps the floating table of contents.
59
+ - id: dark
60
+ label: 3. Dark Backing & Zoom
61
+ text: "Clicking any article figure opens it in a full-screen lightbox (press `Esc` or click the backdrop to close); dark mode sets `--image-backing: #faf9f5` behind figures."
62
+ ```
63
+
64
+ ## 4. Dark-mode Image Backing and Warm Paper Tokens
65
+
66
+ Technical posts frequently include transparent-background architecture diagrams or equation screenshots drawn with dark lines. On a dark page background those dark lines disappear, while a CSS `filter: invert` distorts full-color screenshots.
67
+
68
+ In dark mode, Garden sets `background: var(--image-backing)` (`#faf9f5`) on article figures and lightbox images without applying color inversion or extra padding. Transparent dark-line diagrams stay legible, and opaque screenshots look unchanged. Site favicons share the same backing token, whereas post covers, avatars, and link card thumbnails do not.
69
+
70
+ The site palette is defined in `app/styles/tokens.css` without brand blue:
71
+
72
+ | Token | Light Mode | Dark Mode | Purpose |
73
+ | :--- | :--- | :--- | :--- |
74
+ | `--paper` | `#faf9f5` | `#1f1e1d` | Primary page background (`--image-backing` in dark mode also uses `#faf9f5`) |
75
+ | `--paper-deep` / `--well` | `#f0eee6` | `#262624` | Code block and recessed well background |
76
+ | `--chip` | `#f0eee6` | `#30302e` | Tag pills and button fills |
77
+ | `--card` | `#ffffff` | `#262624` | Elevated cards, figure frames, and dialogs |
78
+ | `--ink` | `#1b1a18` | `#eeece7` | Headings, strong emphasis, and primary interactive ink |
79
+ | `--text` / `--soft` | `#2d2c29` / `#4a4844` | `#dcd9d2` / `#bdb9b0` | Body paragraphs and secondary copy |
80
+ | `--muted` | `#6e6b64` | `#a09b91` | Dates, captions, and table headers (meeting WCAG AA 4.5:1 contrast) |
81
+ | `--rule` / `--rule-strong` | `#e6e3dc` / `#d6d2c9` | `#3d3d3a` / `#4d4c48` | `1px` hairlines, card borders, and emphasized strokes |
@@ -0,0 +1,192 @@
1
+ ---
2
+ title: Write in Markdown | Push to Update the Site
3
+ description: Write posts, short notes, and columns in Markdown inside your own Git repository.
4
+ section: overview
5
+ order: 0
6
+ ---
7
+
8
+ Garden is a Claude-style blog framework where you write posts in Markdown and describe components in plain language.
9
+
10
+ ## A Complete Example
11
+
12
+ The four steps in this section come from a real post, and every command output is taken verbatim from an actual run.
13
+
14
+ ### 1. The Author Writes an Idea
15
+
16
+ Drop an unnamed `garden` fence where the interactive block should go and state what the reader adjusts and sees:
17
+
18
+ ````markdown
19
+ ## Writing Pace
20
+
21
+ ```garden
22
+ Let the reader drag daily word count and writing days per week to see how many weeks an 8,000-word article takes.
23
+ ```
24
+ ````
25
+
26
+ ### 2. The Agent Scans and Writes Back the Component
27
+
28
+ Tell the Agent to compile `content/posts/writing-pace.md`. Following the `garden-ui` skill in the repository, the Agent first runs `npx garden ui scan` to read the line number, original wording, and hash:
29
+
30
+ ```console
31
+ $ npx garden ui scan content/posts/writing-pace.md
32
+ {
33
+ "file": "content/posts/writing-pace.md",
34
+ "draft": false,
35
+ "blocks": [
36
+ {
37
+ "lang": "garden-idea",
38
+ "status": "uncompiled",
39
+ "startLine": 9,
40
+ "intentHash": "8866427a",
41
+ "intent": "Let the reader drag daily word count and writing days per week to see how many weeks an 8,000-word article takes.",
42
+ "section": "Writing Pace"
43
+ }
44
+ ]
45
+ }
46
+ ```
47
+
48
+ Next, the Agent reads the component catalog from `npx garden ui prompt`, writes a `garden interactive` block, and runs `npx garden ui apply` with `--intent-hash` to replace only that fence while leaving the rest of the file byte-for-byte untouched:
49
+
50
+ ```console
51
+ $ npx garden ui apply content/posts/writing-pace.md --intent-hash 8866427a --file new.md
52
+ {"ok":true,"file":"content/posts/writing-pace.md","kind":"garden","line":9}
53
+ ```
54
+
55
+ ### 3. The Pre-commit Hook Checks and Prints a Fix Prompt
56
+
57
+ On the first `garden sync` or `garden dev` run, Garden writes `exec npx --no-install garden ui check --staged --quiet` to `.git/hooks/pre-commit`. If the author commits before step 2, the hook blocks the commit and prints a ready-to-paste prompt for the AI:
58
+
59
+ ```console
60
+ $ git commit -m "Writing pace"
61
+ content/posts/writing-pace.md:9 - garden-idea uncompiled idea remaining
62
+
63
+ ──────────────────────── Fix prompt for AI ────────────────────────
64
+ Handle the Garden post blocks listed below: compile uncompiled ideas, and fix interactive blocks that have errors.
65
+
66
+ Rules:
67
+ - First run npx garden ui prompt and read the component catalog and rules; if the repo has the garden-ui skill, follow its flow.
68
+ - Change only the listed blocks. Write them back with npx garden ui apply <post> --id <id> (use --intent-hash for ideas without an id). Do not edit the post by hand.
69
+ - Keep the author's meaning. When done, run npx garden ui check <post> until it passes.
70
+ - If you cannot access this repository, reply with only the finished fence for each block.
71
+
72
+ 1. content/posts/writing-pace.md, lines 9–11, intent-hash: 8866427a
73
+ - garden-idea: this idea is not compiled yet; compile it into garden interactive or another garden component (or put the sentence back into the prose).
74
+
75
+ Current fence:
76
+
77
+ ```garden
78
+ Let the reader drag daily word count and writing days per week to see how many weeks an 8,000-word article takes.
79
+ ```
80
+ ────────────────────────────────────────────────────────────
81
+ Wrote .garden/ui-fix-prompt.md. To commit anyway, run git commit --no-verify.
82
+ ```
83
+
84
+ The same prompt is saved to `.garden/ui-fix-prompt.md` and copied to the clipboard when a clipboard command is available. Once the Agent applies the block and the author commits again, the hook exits silently with code `0`.
85
+
86
+ ### 4. The Rendered Result
87
+
88
+ The `garden interactive` fence written back in step 2 renders directly on the article page as an interactive calculator:
89
+
90
+ ```garden interactive #writing-pace
91
+ caption: Writing a few hundred extra words each day finishes sooner than adding another writing day per week.
92
+ intent: |
93
+ Let the reader drag daily word count and writing days per week to see how many weeks an 8,000-word article takes.
94
+ ---
95
+ <State daily={500} days={3} />
96
+ <Let weekly={daily * days} />
97
+ <Let weeks={ceil(8000 / weekly)} />
98
+
99
+ <Slider bind="daily" label="Words per day" min={200} max={2000} step={100} unit="words" />
100
+ <Slider bind="days" label="Days per week" min={1} max={7} step={1} unit="days" />
101
+
102
+ <Stat label="Weeks to finish 8,000 words" value={weeks} unit="weeks" hint="Rounded up to full weeks" />
103
+ ```
104
+
105
+ > [!IMPORTANT] Humans and Agents Share the Same Tools
106
+ > The `npx garden ui scan`, `npx garden ui apply`, and `npx garden ui check` commands used by the Agent are the exact commands run by the author's pre-commit hook. When the hook stops a commit, its output is the instruction for the Agent's next step. See [Writing with AI](/garden/en/ai-writing).
107
+
108
+ The remaining sections cover the layout and component rules that work out of the box without an Agent.
109
+
110
+ ## Two-line Titles and the `740px` Reading Column
111
+
112
+ Write ` | ` (with spaces on both sides) inside a title to split it across two lines and scale the font size to fit the longer line. Body copy stays inside a `740px` column, while wide figures expand symmetrically into the side margins without overlapping the navigation or table of contents.
113
+
114
+ Transparent-background line drawings automatically get a warm paper backing in dark mode, so you do not need separate dark assets:
115
+
116
+ ![Diagram of the 740px reading column, 1040px wide figure zone, and 1340px wide table zone (transparent-background dark-line SVG, backed with #faf9f5 in dark mode).](../assets/reading-layout.svg)
117
+
118
+ ## Wide Tables Expand Symmetrically
119
+
120
+ When a table is wider than the reading column without wrapping, it expands into both margins up to `1340px` on desktop:
121
+
122
+ | Content Type | Source Location | How the Page Handles Long-form and Media Details | Typical Use |
123
+ | :--- | :--- | :--- | :--- |
124
+ | Long-form posts | `content/posts/<slug>.md` and a matching asset folder | Reads local image dimensions at build time to prevent layout shift, splits titles on ` \| `, widens figures and tables, and renders KaTeX math and Mermaid diagrams | System design notes, code walkthroughs, debugging postmortems, and book notes |
125
+ | Short thoughts | `content/thoughts/<slug>.md` | Renders multi-image grids, native `image` / `audio` / `video` players, and external `link` preview cards | Short observations, terminal screenshots, audio clips, and saved links |
126
+ | Ordered columns | `content/columns.yaml` | Chains multiple posts from `content/posts/` in reading order with series progress and previous/next navigation | Multi-part tutorials, topic series, and chaptered notes |
127
+
128
+ ## Code Blocks and the Diff Toggle
129
+
130
+ Write a one-sentence `garden` idea right below an updated code block to describe what changed, and the Agent generates the adjacent `diff` fence; you can also write the `diff` fence by hand. When a code block is immediately followed by a `diff` block, the page combines them into a single frame with tabs for the full code and the diff against the previous version, while the copy button always copies the new code.
131
+
132
+ ```yaml
133
+ pages:
134
+ garden:
135
+ title: "Write in Markdown | Push to Update the Site"
136
+ subtitle: "Write posts, short notes, and columns in Markdown inside your own Git repository."
137
+ ```
138
+
139
+ ```diff
140
+ pages:
141
+ garden:
142
+ - title: "Write in Markdown.\nPush to update the site."
143
+ - subtitle: ""
144
+ + title: "Write in Markdown | Push to Update the Site"
145
+ + subtitle: "Write posts, short notes, and columns in Markdown inside your own Git repository."
146
+ ```
147
+
148
+ > [!NOTE] Note
149
+ > A `diff` fence must sit immediately after a complete code block; a standalone `diff` fence triggers a `diff-orphan` error in `npx garden check`.
150
+
151
+ > [!TIP] Custom Callout Titles
152
+ > Write text on the same line after `> [!TIP]` (or place a bold line immediately below) to replace the default callout title. Supported types include `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, and `CAUTION`.
153
+
154
+ ## Structured Components in Markdown
155
+
156
+ Use ```` ```garden <name> ```` fences to insert metrics, side-by-side comparisons, timelines, or charts. Here is a `garden stat` block:
157
+
158
+ ```garden stat
159
+ items:
160
+ - label: Reading Column
161
+ value: "740"
162
+ unit: px
163
+ caption: Default width for paragraphs, code blocks, and lists
164
+ - label: Wide Figure Zone
165
+ value: "1040"
166
+ unit: px
167
+ caption: Screenshots display at half their pixel width and expand up to this limit above ~2000px
168
+ - label: Wide Table Zone
169
+ value: "1340"
170
+ unit: px
171
+ caption: Maximum width for wide multi-column tables expanding symmetrically on desktop
172
+ ```
173
+
174
+ See [Components Overview](/garden/en/components) in the sidebar for all 9 built-in components and their live examples.
175
+
176
+ <h2 id="garden-admin-title">From Init to Daily Writing</h2>
177
+
178
+ Your site repository stores only configuration, Markdown posts, and static assets, while `hd-garden` provides the renderer and build scripts. Initialize and preview a site locally:
179
+
180
+ ```bash
181
+ npx hd-garden init my-blog
182
+ cd my-blog
183
+ npm install
184
+ make dev
185
+ ```
186
+
187
+ Run `make update` when a post is ready; it runs the full check and build before committing and pushing. Read the chapters for details:
188
+
189
+ - **[Quick Start](/garden/en/start)**: Directory layout, creating posts with `make new`, previewing drafts, and daily commands.
190
+ - **[Site & Column Config](/garden/en/config)**: Every field in `site.config.yaml`, custom SVG icons, and ordering series in `content/columns.yaml`.
191
+ - **[Deploy to GitHub Pages](/garden/en/deploy)**: A single GitHub Actions workflow that supports both one public repository and a private source repo paired with a public Pages repo.
192
+ - **[Writing Posts & Thoughts](/garden/en/writing)** and **[How the Reading Layout Works](/garden/en/design)**: Front Matter fields, external link cards, three-tier adaptive widths, and color tokens.
@@ -0,0 +1,92 @@
1
+ ---
2
+ title: Quick Start
3
+ navTitle: Quick Start
4
+ description: Initialize a site directory with npx hd-garden init, preview locally, create posts, and push updates.
5
+ section: getting-started
6
+ order: 1
7
+ ---
8
+
9
+ Garden is a personal blog tool written in Markdown and managed with Git. An initialized site repository holds only your configuration files, Markdown content, and static assets, while the rendering and build pipeline comes from the `hd-garden` package.
10
+
11
+ ## 1. Requirements and Initialization
12
+
13
+ Install **Node.js `>= 22.13.0`**, **Git**, and **GNU Make** first. Run the following commands in your terminal to create a site directory:
14
+
15
+ ```bash
16
+ npx hd-garden init my-blog
17
+ cd my-blog
18
+ npm install
19
+ make dev
20
+ ```
21
+
22
+ `npx hd-garden init` copies the site template into `my-blog` and writes the helper skills under `.agents/skills/`. It does not run `npm install` automatically, and it does not create `.github/workflows/pages.yml` (see [Deploy to GitHub Pages](/garden/en/deploy) for the workflow file).
23
+
24
+ On the first `make dev` run, if `node_modules` is missing, `make prepare` in the `Makefile` runs `npm install` before invoking `npx garden dev` (which syncs content first) and starting the local dev server at `http://localhost:3000`.
25
+
26
+ ## 2. Directory Structure
27
+
28
+ An initialized site repository has the following layout:
29
+
30
+ ```text
31
+ my-blog/
32
+ ├── site.config.yaml # Site name, author info, and titles/subtitles for top-level pages
33
+ ├── content/
34
+ │ ├── columns.yaml # Column definitions and reading order
35
+ │ ├── covers/ # Cover images for posts and columns
36
+ │ ├── posts/ # Long-form posts (.md) and matching sidecar folders
37
+ │ └── thoughts/ # Short notes (.md) and media attachments
38
+ ├── public/ # Static files served at their root paths (such as icons)
39
+ ├── Makefile # Daily commands for previewing, creating posts, checking, and pushing
40
+ └── package.json # Depends only on hd-garden
41
+ ```
42
+
43
+ ## 3. Creating Posts and Thoughts
44
+
45
+ ### Creating a Post (`make new`)
46
+
47
+ Run `make new` to create a draft in `content/posts/` pre-filled with the date, title, and a commented component cheat sheet:
48
+
49
+ ```bash
50
+ make new SLUG=tcp-bbr TITLE="TCP BBR Notes"
51
+ ```
52
+
53
+ To place the post inside a subdirectory (such as `content/posts/network/tcp-bbr.md`), pass `DIR`:
54
+
55
+ ```bash
56
+ make new DIR=network SLUG=tcp-bbr TITLE="TCP BBR Notes"
57
+ ```
58
+
59
+ > [!NOTE] Note
60
+ > `SLUG` may only contain lowercase letters, digits, and hyphens (`a-z`, `0-9`, `-`), never `/`; pass subdirectories via `DIR`. `make new` reads only `SLUG`, `TITLE`, and `DIR`.
61
+
62
+ Newly created files default to `draft: true`. Set `CONTENT_INCLUDE_DRAFTS=1` to preview drafts or future-dated (`publishAt`) posts locally:
63
+
64
+ ```bash
65
+ CONTENT_INCLUDE_DRAFTS=1 make dev
66
+ ```
67
+
68
+ ### Creating a Thought
69
+
70
+ Thoughts live in `content/thoughts/`. Create a `.md` file directly in `content/thoughts/` (for example `content/thoughts/2026-10-19-note.md`) with `title` and `date` in its Front Matter, or publish from the browser after configuring `/admin`. See [Writing Posts & Thoughts](/garden/en/writing) for the supported fields.
71
+
72
+ ## 4. Daily Commands and Upgrading
73
+
74
+ The site `Makefile` and `garden` CLI provide these commands:
75
+
76
+ | Command | What It Runs |
77
+ | :--- | :--- |
78
+ | `make dev` | Runs `make prepare` to check dependencies, then runs `npx garden dev` (which syncs content and starts the dev server) |
79
+ | `CONTENT_INCLUDE_DRAFTS=1 make dev` | Previews `draft: true` drafts and future `publishAt` posts alongside published content |
80
+ | `make new SLUG=xxx TITLE="Title" DIR=subdir` | Calls `npx garden new` to create a draft post under `content/posts/` (`DIR` is optional) |
81
+ | `make check` | Runs `npx garden sync && npx garden build` in the site repo to verify content sync and the production build |
82
+ | `npx garden check content/posts/xxx.md` | Checks a single Markdown post's title, component fields, code diffs, and local asset paths |
83
+ | `make update MESSAGE="Commit message"` | Runs `make check`, then `git add -A`, `git commit -m "$(MESSAGE)"`, and `git push origin HEAD` |
84
+
85
+ ### Upgrading `hd-garden`
86
+
87
+ To upgrade the engine, edit `package.json` in your site root, update the `"hd-garden"` version string, and run `npm install`:
88
+
89
+ ```bash
90
+ npm install
91
+ make check
92
+ ```