for-humanity 0.11.0 → 0.12.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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.12.0 — 2026-10-13
6
+
7
+ - Add a `[file name]` fence meta that draws a title bar with the file name and language above the code block, sharing the `bad`/`good` tone.
8
+
5
9
  ## 0.11.0 — 2026-10-13
6
10
 
7
11
  - Add `<<< ./file.ext#region meta` code snippet imports: a file inside the document root becomes a code block, with optional `#region` extraction, dedent, fence meta passthrough and dev reloads when the file changes.
package/README.md CHANGED
@@ -33,7 +33,7 @@ Write Markdown. Keep the context. Share a static site.
33
33
  Node.js 22 or later and pnpm. Install the CLI in your document project:
34
34
 
35
35
  ```sh
36
- pnpm add -D --save-exact for-humanity@0.11.0
36
+ pnpm add -D --save-exact for-humanity@0.12.0
37
37
  mkdir docs
38
38
  ```
39
39
 
@@ -52,7 +52,7 @@ pnpm exec for-humanity preview docs
52
52
 
53
53
  If your Markdown files are in the project root, use `.` instead of `docs`. The root `README.md` becomes the home page; root `AGENTS.md` and `CLAUDE.md` stay out of the site.
54
54
 
55
- Version `0.11.0` supports `dev`, `build` and `preview`. `init` is planned; see the [Roadmap](https://github.com/minu-ha/for-humanity/blob/main/templates/document/roadmap.md) and [Changelog](https://github.com/minu-ha/for-humanity/blob/main/CHANGELOG.md).
55
+ Version `0.12.0` supports `dev`, `build` and `preview`. `init` is planned; see the [Roadmap](https://github.com/minu-ha/for-humanity/blob/main/templates/document/roadmap.md) and [Changelog](https://github.com/minu-ha/for-humanity/blob/main/CHANGELOG.md).
56
56
  Cloudflare Pages can host the generated site. See [Deployment](https://github.com/minu-ha/for-humanity/blob/main/templates/document/deployment.md) for the build settings.
57
57
  Set an optional public root `url` in the site configuration to generate `robots.txt` and `sitemap.xml`; projects without a URL emit neither file. See [Crawling](https://github.com/minu-ha/for-humanity/blob/main/templates/document/crawling.md).
58
58
 
@@ -75,7 +75,7 @@ pnpm preview # Preview the static site
75
75
  pnpm build:pages # Add Cloudflare Pages headers and a 404 page
76
76
  ```
77
77
 
78
- Changes pushed to `main` update the documentation site. A stable version tag such as `v0.11.0` runs checks, publishes the package to npm, then creates a GitHub Release with the same package file.
78
+ Changes pushed to `main` update the documentation site. A stable version tag such as `v0.12.0` runs checks, publishes the package to npm, then creates a GitHub Release with the same package file.
79
79
  The npm README updates with each package release. See [Maintenance](https://github.com/minu-ha/for-humanity/blob/main/templates/document/maintenance.md#packaging-and-release) for the release steps.
80
80
 
81
81
  ## Make it yours
package/dist/cli.css CHANGED
@@ -203,6 +203,31 @@
203
203
  border-radius: var(--app-radius-inline);
204
204
  background: var(--app-color-accent-soft);
205
205
  }
206
+ & .wg_prose__codeFile {
207
+ margin: var(--app-space-block) 0;
208
+ }
209
+ & .wg_prose__codeFile pre {
210
+ margin: 0;
211
+ border-top-left-radius: 0;
212
+ border-top-right-radius: 0;
213
+ }
214
+ & .wg_prose__codeFileTitle {
215
+ display: flex;
216
+ gap: var(--app-space-block);
217
+ justify-content: space-between;
218
+ padding: var(--app-space-inline) var(--app-space-block);
219
+ border: 1px solid var(--app-color-border);
220
+ border-bottom: 0;
221
+ border-radius: var(--app-radius-box) var(--app-radius-box) 0 0;
222
+ background: var(--app-code-background);
223
+ color: var(--app-color-text-muted);
224
+ font-family: var(--app-font-mono);
225
+ font-size: var(--app-font-size-dense);
226
+ letter-spacing: 0;
227
+ }
228
+ & .wg_prose__codeFileLang {
229
+ text-transform: uppercase;
230
+ }
206
231
  & pre.wg_prose__lineNumbers code {
207
232
  counter-reset: line;
208
233
  }
@@ -886,10 +911,12 @@
886
911
  --app-space-gutter: 16px;
887
912
  }
888
913
  }
889
- .wg_prose__code--bad {
914
+ .wg_prose__code--bad,
915
+ .wg_prose__codeFile--bad {
890
916
  --app-code-background: var(--app-color-bad-soft);
891
917
  }
892
- .wg_prose__code--good {
918
+ .wg_prose__code--good,
919
+ .wg_prose__codeFile--good {
893
920
  --app-code-background: var(--app-color-good-soft);
894
921
  }
895
922
 
package/dist/cli.js CHANGED
@@ -1880,6 +1880,33 @@ var tone = {
1880
1880
  }
1881
1881
  }
1882
1882
  };
1883
+ var fileTitle = {
1884
+ name: "for-humanity:code-title",
1885
+ root(root) {
1886
+ const meta = this.options.meta?.__raw;
1887
+ const title = typeof meta === "string" ? /\[([^\]]+)\]/.exec(meta)?.[1] : void 0;
1888
+ if (title === void 0) {
1889
+ return;
1890
+ }
1891
+ const tones = typeof meta === "string" ? ["bad", "good"].filter((word) => new RegExp(`\\b${word}\\b`).test(meta)) : [];
1892
+ const caption = {
1893
+ type: "element",
1894
+ tagName: "figcaption",
1895
+ properties: { className: ["wg_prose__codeFileTitle"] },
1896
+ children: [
1897
+ { type: "text", value: title },
1898
+ { type: "element", tagName: "span", properties: { className: ["wg_prose__codeFileLang"] }, children: [{ type: "text", value: this.options.lang }] }
1899
+ ]
1900
+ };
1901
+ const figure = {
1902
+ type: "element",
1903
+ tagName: "figure",
1904
+ properties: { className: ["wg_prose__codeFile", ...tones.map((tone2) => `wg_prose__codeFile--${tone2}`)] },
1905
+ children: [caption, ...root.children.filter((child) => child.type !== "doctype")]
1906
+ };
1907
+ root.children = [figure];
1908
+ }
1909
+ };
1883
1910
  var shikiTransformers = [
1884
1911
  transformerNotationHighlight({ matchAlgorithm: "v3" }),
1885
1912
  transformerNotationErrorLevel({ matchAlgorithm: "v3" }),
@@ -1887,7 +1914,8 @@ var shikiTransformers = [
1887
1914
  transformerNotationWordHighlight({ matchAlgorithm: "v3" }),
1888
1915
  transformerMetaHighlight(),
1889
1916
  lineNumbers,
1890
- tone
1917
+ tone,
1918
+ fileTitle
1891
1919
  ];
1892
1920
 
1893
1921
  // src/entry/cli/_function/create-processor/create-processor.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "for-humanity",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Documents written for humans to read: a docs kit with a sidebar, parts, sections and flowcharts, built on Hono JSX",
5
5
  "keywords": [
6
6
  "docs",
@@ -263,6 +263,36 @@
263
263
  background: var(--app-color-accent-soft);
264
264
  }
265
265
 
266
+ /* 파일 이름 띠 · 펜스 메타 [파일 이름] · 코드 상자와 한 몸, 여백은 figure 가 맡음 */
267
+ & .wg_prose__codeFile {
268
+ margin: var(--app-space-block) 0;
269
+ }
270
+
271
+ & .wg_prose__codeFile pre {
272
+ margin: 0;
273
+ border-top-left-radius: 0;
274
+ border-top-right-radius: 0;
275
+ }
276
+
277
+ & .wg_prose__codeFileTitle {
278
+ display: flex;
279
+ gap: var(--app-space-block);
280
+ justify-content: space-between;
281
+ padding: var(--app-space-inline) var(--app-space-block);
282
+ border: 1px solid var(--app-color-border);
283
+ border-bottom: 0;
284
+ border-radius: var(--app-radius-box) var(--app-radius-box) 0 0;
285
+ background: var(--app-code-background);
286
+ color: var(--app-color-text-muted);
287
+ font-family: var(--app-font-mono);
288
+ font-size: var(--app-font-size-dense);
289
+ letter-spacing: 0;
290
+ }
291
+
292
+ & .wg_prose__codeFileLang {
293
+ text-transform: uppercase;
294
+ }
295
+
266
296
  /* 줄 번호 · 펜스 메타 line-numbers · 복사에 섞이지 않게 content 로 */
267
297
  & pre.wg_prose__lineNumbers code {
268
298
  counter-reset: line;
@@ -5,6 +5,7 @@ import {
5
5
  transformerNotationHighlight,
6
6
  transformerNotationWordHighlight,
7
7
  } from "@shikijs/transformers";
8
+ import type {Element, ElementContent} from "hast";
8
9
  import type {ShikiTransformer} from "shiki";
9
10
 
10
11
  /**
@@ -42,7 +43,41 @@ const tone: ShikiTransformer = {
42
43
  };
43
44
 
44
45
  /**
45
- * 코드 줄 표기 · `// [!code highlight|error|warning|++|--]`, `// [!code word:x]`, 메타 `{2,4-6}`, `line-numbers`, 블록 색 `bad`·`good`
46
+ * 펜스 메타 `[파일 이름]` → figure 로 감싸고 제목 띠에 파일 이름과 언어 · 블록 색 클래스는 figure 에도 둠
47
+ */
48
+ const fileTitle: ShikiTransformer = {
49
+ name: "for-humanity:code-title",
50
+ root(root) {
51
+ const meta = this.options.meta?.__raw;
52
+ const title = typeof meta === "string" ? /\[([^\]]+)\]/.exec(meta)?.[1] : undefined;
53
+
54
+ if (title === undefined) {
55
+ return;
56
+ }
57
+
58
+ const tones = typeof meta === "string" ? ["bad", "good"].filter((word) => new RegExp(`\\b${word}\\b`).test(meta)) : [];
59
+ const caption: Element = {
60
+ type: "element",
61
+ tagName: "figcaption",
62
+ properties: {className: ["wg_prose__codeFileTitle"]},
63
+ children: [
64
+ {type: "text", value: title},
65
+ {type: "element", tagName: "span", properties: {className: ["wg_prose__codeFileLang"]}, children: [{type: "text", value: this.options.lang}]},
66
+ ],
67
+ };
68
+ const figure: Element = {
69
+ type: "element",
70
+ tagName: "figure",
71
+ properties: {className: ["wg_prose__codeFile", ...tones.map((tone) => `wg_prose__codeFile--${tone}`)]},
72
+ children: [caption, ...root.children.filter((child): child is ElementContent => child.type !== "doctype")],
73
+ };
74
+
75
+ root.children = [figure];
76
+ },
77
+ };
78
+
79
+ /**
80
+ * 코드 줄 표기 · `// [!code highlight|error|warning|++|--]`, `// [!code word:x]`, 메타 `{2,4-6}`, `line-numbers`, 블록 색 `bad`·`good`, 제목 `[파일 이름]`
46
81
  */
47
82
  export const shikiTransformers: ShikiTransformer[] = [
48
83
  transformerNotationHighlight({matchAlgorithm: "v3"}),
@@ -52,4 +87,5 @@ export const shikiTransformers: ShikiTransformer[] = [
52
87
  transformerMetaHighlight(),
53
88
  lineNumbers,
54
89
  tone,
90
+ fileTitle,
55
91
  ];
@@ -96,6 +96,28 @@ test("code notation marks lines, meta highlights lines, and line-numbers tags th
96
96
  }
97
97
  });
98
98
 
99
+ test("a bracketed fence meta renders a file title bar that carries the block tone", async () => {
100
+ const root = await mkdtemp(join(tmpdir(), "for-humanity-code-title-"));
101
+
102
+ try {
103
+ await writeFile(
104
+ join(root, "guide.md"),
105
+ "---\nname: Guide\nlabel: Guide\ngroup: Guide\n---\n\n## Read\n\n```ts bad [page/products.ts]\nconst a = 1;\n```\n\n```ts {1}\nconst b = 2;\n```\n",
106
+ );
107
+ const site = siteConfigSchema.parse({});
108
+ const [doc] = await readDocs({root, processor: createProcessor({site, root})});
109
+
110
+ assert.ok(doc);
111
+ assert.match(
112
+ doc.html,
113
+ /<figure class="wg_prose__codeFile wg_prose__codeFile--bad"><figcaption class="wg_prose__codeFileTitle">page\/products\.ts<span class="wg_prose__codeFileLang">ts<\/span><\/figcaption><pre class="[^"]*wg_prose__code--bad/,
114
+ );
115
+ assert.equal(doc.html.split("<figure").length - 1, 1, "braces meta is not a title");
116
+ } finally {
117
+ await rm(root, {recursive: true, force: true});
118
+ }
119
+ });
120
+
99
121
  test("code snippets import a file or a region from inside the document root and pass fence meta", async () => {
100
122
  const root = await mkdtemp(join(tmpdir(), "for-humanity-snippets-"));
101
123
 
@@ -176,11 +176,13 @@
176
176
  }
177
177
  }
178
178
 
179
- /* 블록 색 · 펜스 메타 bad·good · Shiki 가 pre 에 인라인으로 쓰는 코드 바탕을 바꿈 */
180
- .wg_prose__code--bad {
179
+ /* 블록 색 · 펜스 메타 bad·good · Shiki 가 pre 에 인라인으로 쓰는 코드 바탕과 제목 띠의 바탕을 바꿈 */
180
+ .wg_prose__code--bad,
181
+ .wg_prose__codeFile--bad {
181
182
  --app-code-background: var(--app-color-bad-soft);
182
183
  }
183
184
 
184
- .wg_prose__code--good {
185
+ .wg_prose__code--good,
186
+ .wg_prose__codeFile--good {
185
187
  --app-code-background: var(--app-color-good-soft);
186
188
  }