for-humanity 0.1.0 → 0.2.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
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 — 2026-10-04
4
+
5
+ - Support nested navigation with `group` name arrays while keeping single-name groups compatible.
6
+ - Order nested sibling groups with paths in `navigation`, independently of document folders.
7
+ - Extend active, hover and keyboard focus tree paths through ancestor groups.
8
+
9
+ ## 0.1.2 — 2026-10-03
10
+
11
+ Documentation and release automation update. The CLI commands and Node.js requirement are unchanged.
12
+
13
+ - Link the README to the public documentation site, npm package and GitHub Releases.
14
+ - Set the package homepage to `https://for-humanity.fyi`.
15
+ - Deploy the documentation from `main` to Cloudflare Pages and preserve installation commands in served HTML.
16
+ - Verify pull requests and publish npm plus GitHub Release from matching stable version tags using Trusted Publishing.
17
+ - Add official Releases and Changelog pages to the documentation site, with the changelog generated from this file.
18
+ - Accept both npm 11 array reports and npm 12 package-keyed reports when validating release artifacts.
19
+
20
+ The `v0.1.1` attempt stopped during package validation and was not published to npm.
21
+
3
22
  ## 0.1.0 — 2026-10-03
4
23
 
5
24
  First public release of the Markdown documentation CLI. Requires Node.js 22 or later.
package/README.md CHANGED
@@ -8,7 +8,10 @@
8
8
 
9
9
  Write Markdown. Keep the context. Share a static site.
10
10
 
11
+ [Documentation](https://for-humanity.fyi) · [npm](https://www.npmjs.com/package/for-humanity) · [Releases](https://github.com/minu-ha/for-humanity/releases)
12
+
11
13
  [![MIT](https://img.shields.io/badge/license-MIT-1111aa?style=flat-square)](https://github.com/minu-ha/for-humanity/blob/main/LICENSE)
14
+ [![npm](https://img.shields.io/npm/v/for-humanity?style=flat-square)](https://www.npmjs.com/package/for-humanity)
12
15
  ![Node 22+](https://img.shields.io/badge/node-22%2B-444444?style=flat-square)
13
16
  ![Hono + React](https://img.shields.io/badge/Hono-%2B%20React-444444?style=flat-square)
14
17
  ![Prototype](https://img.shields.io/badge/status-prototype-9a6400?style=flat-square)
@@ -30,7 +33,7 @@ Write Markdown. Keep the context. Share a static site.
30
33
  Node.js 22 or later and pnpm. Install the CLI in your document project:
31
34
 
32
35
  ```sh
33
- pnpm add -D --save-exact for-humanity@0.1.0
36
+ pnpm add -D --save-exact for-humanity@0.2.0
34
37
  mkdir docs
35
38
  ```
36
39
 
@@ -49,7 +52,7 @@ pnpm exec for-humanity preview docs
49
52
 
50
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.
51
54
 
52
- Version `0.1.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.2.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).
53
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.
54
57
 
55
58
  ### Develop the kit
@@ -71,11 +74,14 @@ pnpm preview # Preview the static site
71
74
  pnpm build:pages # Add Cloudflare Pages headers and a 404 page
72
75
  ```
73
76
 
77
+ Changes pushed to `main` update the documentation site. A stable version tag such as `v0.2.0` runs checks, publishes the package to npm, then creates a GitHub Release with the same package file.
78
+ 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.
79
+
74
80
  ## Make it yours
75
81
 
76
82
  A document folder is enough to get started. Without a site configuration, the sidebar shows **for humanity**. The pixel face appears in the browser tab and follows the mouse on desktop.
77
83
  The folder's `README.md` becomes the home page at `/`, with no frontmatter required. Click the site name to return home.
78
- This repository generates [the documentation home](https://github.com/minu-ha/for-humanity/blob/main/templates/document/README.md) from its root README.md. Edit the repository's root README.md, then run `pnpm sync:readme`; `pnpm dev` and `pnpm build` also sync it on startup.
84
+ This repository generates [the documentation home](https://github.com/minu-ha/for-humanity/blob/main/templates/document/README.md) from its root README.md and [the site changelog](https://github.com/minu-ha/for-humanity/blob/main/templates/document/changelog.md) from CHANGELOG.md. Edit those source files, then run `pnpm sync:docs`; `pnpm dev` and `pnpm build` also sync them on startup.
79
85
  Set your own name in `for-humanity.config.mjs`:
80
86
 
81
87
  ```js
@@ -88,6 +94,7 @@ export default {
88
94
 
89
95
  See [Commands](https://github.com/minu-ha/for-humanity/blob/main/templates/document/commands.md) for using another document folder, and [Settings](https://github.com/minu-ha/for-humanity/blob/main/templates/document/settings.md) for configuration.
90
96
  Each document other than the root README chooses a `group` and optional `order`. Pages stay independent; groups do not need parent documents or nested folders. Document names may share the same first letter.
97
+ Use `group: Guide` for one group or `group: [Projects, Example project, Research]` for nested navigation. Group paths do not change document URLs.
91
98
 
92
99
  ## Write with context
93
100
 
@@ -110,6 +117,7 @@ The contract, code or reasoning behind the visible result.
110
117
  | Getting started | [Commands](https://github.com/minu-ha/for-humanity/blob/main/templates/document/commands.md) · [Deployment](https://github.com/minu-ha/for-humanity/blob/main/templates/document/deployment.md) |
111
118
  | Guide | [Writing](https://github.com/minu-ha/for-humanity/blob/main/templates/document/writing.md) · [Parts](https://github.com/minu-ha/for-humanity/blob/main/templates/document/parts.md) · [Settings](https://github.com/minu-ha/for-humanity/blob/main/templates/document/settings.md) |
112
119
  | Reference | [API](https://github.com/minu-ha/for-humanity/blob/main/templates/document/api.md) |
120
+ | Releases | [Official releases](https://github.com/minu-ha/for-humanity/blob/main/templates/document/releases.md) · [Changelog](https://github.com/minu-ha/for-humanity/blob/main/templates/document/changelog.md) |
113
121
  | Examples | [Fictional Blueprint](https://github.com/minu-ha/for-humanity/blob/main/templates/document/blueprint.md) |
114
122
  | Development | [Architecture](https://github.com/minu-ha/for-humanity/blob/main/templates/document/architecture.md) · [Design](https://github.com/minu-ha/for-humanity/blob/main/templates/document/design.md) · [Maintenance](https://github.com/minu-ha/for-humanity/blob/main/templates/document/maintenance.md) · [Roadmap](https://github.com/minu-ha/for-humanity/blob/main/templates/document/roadmap.md) |
115
123
 
package/dist/cli.css CHANGED
@@ -295,22 +295,32 @@
295
295
  }
296
296
  }
297
297
 
298
- /* src/component/widget/shell/_wg-shell-nav.css */
299
- .wg_shellNav__root {
300
- font-size: var(--app-font-size-dense);
301
- line-height: var(--app-line-height-tight);
302
- }
303
- .wg_shellNav__docs {
304
- display: flex;
305
- flex-direction: column;
306
- gap: var(--app-space-stack);
307
- }
308
- .wg_shellNav__list {
298
+ /* src/component/widget/shell/_wg-shell-nav-list.css */
299
+ .wg_shellNavList__root {
309
300
  margin: 0 0 0 var(--app-space-tree-inset);
310
301
  padding: 0;
311
302
  list-style: none;
312
303
  }
313
- .wg_shellNav__item {
304
+ .wg_shellNavList__branch {
305
+ position: relative;
306
+ display: block;
307
+ padding: var(--app-space-tree-row) 0 var(--app-space-tree-row) calc(var(--app-space-tree-indent) + var(--app-space-inline));
308
+ color: var(--app-color-text-muted);
309
+ font-size: var(--app-font-size-label);
310
+ line-height: var(--app-line-height-tight);
311
+ overflow-wrap: anywhere;
312
+ &::before {
313
+ position: absolute;
314
+ top: calc(0.5lh + var(--app-space-tree-row));
315
+ left: 0;
316
+ width: var(--app-space-tree-indent);
317
+ border-top: 1px solid var(--app-color-border-soft);
318
+ content: "";
319
+ pointer-events: none;
320
+ }
321
+ }
322
+ .wg_shellNavList__item {
323
+ display: flow-root;
314
324
  position: relative;
315
325
  &::before {
316
326
  position: absolute;
@@ -340,24 +350,36 @@
340
350
  &:where(:has(:focus-visible))::after {
341
351
  border-left-color: var(--app-color-text-strong);
342
352
  }
343
- &:where(:has(~ .wg_shellNav__item:hover))::before {
353
+ &:where(:has(~ .wg_shellNavList__item:hover))::before {
344
354
  border-left-color: var(--app-color-text-strong);
345
355
  }
346
- &:where(:has(~ .wg_shellNav__item :focus-visible))::before {
356
+ &:where(:has(~ .wg_shellNavList__item :focus-visible))::before {
347
357
  border-left-color: var(--app-color-text-strong);
348
358
  }
359
+ & > .wg_shellNavList__root {
360
+ margin-left: var(--app-space-tree-indent);
361
+ }
362
+ &:where(:hover) > .wg_shellNavList__branch::before {
363
+ border-top-color: var(--app-color-text-strong);
364
+ }
365
+ &:where(:has(:focus-visible)) > .wg_shellNavList__branch::before {
366
+ border-top-color: var(--app-color-text-strong);
367
+ }
349
368
  }
350
- .wg_shellNav__item--trail {
369
+ .wg_shellNavList__item--trail {
351
370
  &::before {
352
371
  border-left-color: var(--app-color-text-strong);
353
372
  }
354
373
  }
355
- .wg_shellNav__item--current {
374
+ .wg_shellNavList__item--current {
356
375
  &::after {
357
376
  border-left-color: var(--app-color-text-strong);
358
377
  }
378
+ & > .wg_shellNavList__branch::before {
379
+ border-top-color: var(--app-color-text-strong);
380
+ }
359
381
  }
360
- .wg_shellNav__link {
382
+ .wg_shellNavList__link {
361
383
  position: relative;
362
384
  display: block;
363
385
  padding: var(--app-space-tree-row) 0 var(--app-space-tree-row) calc(var(--app-space-tree-indent) + var(--app-space-inline));
@@ -365,6 +387,7 @@
365
387
  font-size: var(--app-font-size-dense);
366
388
  line-height: var(--app-line-height-tight);
367
389
  text-decoration: none;
390
+ overflow-wrap: anywhere;
368
391
  &::before {
369
392
  position: absolute;
370
393
  top: calc(0.5lh + var(--app-space-tree-row));
@@ -388,13 +411,24 @@
388
411
  border-top-color: currentcolor;
389
412
  }
390
413
  }
391
- .wg_shellNav__link--active {
414
+ .wg_shellNavList__link--active {
392
415
  color: var(--app-color-text-strong);
393
416
  font-weight: var(--app-font-weight-active);
394
417
  &::before {
395
418
  border-top-color: currentcolor;
396
419
  }
397
420
  }
421
+
422
+ /* src/component/widget/shell/_wg-shell-nav.css */
423
+ .wg_shellNav__root {
424
+ font-size: var(--app-font-size-dense);
425
+ line-height: var(--app-line-height-tight);
426
+ }
427
+ .wg_shellNav__docs {
428
+ display: flex;
429
+ flex-direction: column;
430
+ gap: var(--app-space-stack);
431
+ }
398
432
  .wg_shellNav__group {
399
433
  margin: 0 0 4px;
400
434
  color: var(--app-color-text-muted);
package/dist/cli.js CHANGED
@@ -30,7 +30,7 @@ var WgProse = (props) => {
30
30
  };
31
31
 
32
32
  // src/component/widget/shell/wg-shell.tsx
33
- import clsx4 from "clsx";
33
+ import clsx5 from "clsx";
34
34
 
35
35
  // src/component/widget/shell/_function/to-toc-groups.ts
36
36
  import { takeWhile } from "es-toolkit/array";
@@ -66,7 +66,50 @@ var toTocGroups = (outline) => {
66
66
  };
67
67
 
68
68
  // src/component/widget/shell/_wg-shell-nav.tsx
69
+ import clsx3 from "clsx";
70
+
71
+ // src/component/widget/shell/_wg-shell-nav-list.tsx
69
72
  import clsx2 from "clsx";
73
+ import { Fragment } from "react";
74
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
75
+ var WgShellNavList = (props) => {
76
+ const entries = [
77
+ ...props.group.docs.map((doc) => ({ kind: "document", key: `doc:${doc.id}`, doc, current: doc.id === props.currentDoc?.id })),
78
+ ...props.group.groups.map((group) => ({
79
+ kind: "group",
80
+ key: `group:${JSON.stringify(group.path)}`,
81
+ group,
82
+ current: group.path.every((name, index) => name === props.currentDoc?.data.group[index])
83
+ }))
84
+ ];
85
+ const currentIndex = entries.findIndex((entry) => entry.current);
86
+ return /* @__PURE__ */ jsx2("ul", { className: clsx2("wg_shellNavList__root"), "aria-label": props.group.name, children: entries.map((entry, index) => /* @__PURE__ */ jsxs2(
87
+ "li",
88
+ {
89
+ className: clsx2("wg_shellNavList__item", {
90
+ "wg_shellNavList__item--trail": index < currentIndex,
91
+ "wg_shellNavList__item--current": entry.current
92
+ }),
93
+ children: [
94
+ entry.kind === "document" && /* @__PURE__ */ jsx2(
95
+ "a",
96
+ {
97
+ className: clsx2("wg_shellNavList__link", { "wg_shellNavList__link--active": entry.current }),
98
+ href: `/${entry.doc.id}/`,
99
+ title: entry.doc.data.label,
100
+ "aria-current": entry.current ? "page" : void 0,
101
+ children: entry.doc.data.name
102
+ }
103
+ ),
104
+ entry.kind === "group" && /* @__PURE__ */ jsxs2(Fragment, { children: [
105
+ /* @__PURE__ */ jsx2("span", { className: clsx2("wg_shellNavList__branch"), children: entry.group.name }),
106
+ /* @__PURE__ */ jsx2(WgShellNavList, { group: entry.group, currentDoc: props.currentDoc })
107
+ ] })
108
+ ]
109
+ },
110
+ entry.key
111
+ )) });
112
+ };
70
113
 
71
114
  // src/constant/copy.ts
72
115
  var copy_site_title_default = "for humanity";
@@ -95,61 +138,60 @@ var copy_warn_unknown_status = "\uC124\uC815\uC5D0 \uC5C6\uB294 \uC0C1\uD0DC \uB
95
138
  var locale_doc_name = "en";
96
139
  var locale_doc_group = "ko";
97
140
 
98
- // src/content/to-doc-groups.ts
141
+ // src/content/to-doc-groups/_to-doc-group-branches.ts
142
+ var toDocGroupBranches = (options) => {
143
+ return [
144
+ ...Map.groupBy(
145
+ options.docs.filter((doc) => doc.data.group.length > options.path.length),
146
+ (doc) => doc.data.group[options.path.length]
147
+ )
148
+ ].map(([name, docs]) => {
149
+ const path = [...options.path, name];
150
+ return {
151
+ name,
152
+ path,
153
+ docs,
154
+ position: options.positions.get(JSON.stringify(path)) ?? options.unlistedPosition
155
+ };
156
+ }).toSorted((a, b) => a.position - b.position || a.name.localeCompare(b.name, locale_doc_group)).map((group) => {
157
+ return {
158
+ name: group.name,
159
+ path: group.path,
160
+ docs: group.docs.filter((doc) => doc.data.group.length === group.path.length).toSorted((a, b) => a.data.order - b.data.order || a.data.name.localeCompare(b.data.name, locale_doc_name) || a.id.localeCompare(b.id, locale_doc_name)),
161
+ groups: toDocGroupBranches({ docs: group.docs, path: group.path, positions: options.positions, unlistedPosition: options.unlistedPosition })
162
+ };
163
+ });
164
+ };
165
+
166
+ // src/content/to-doc-groups/to-doc-groups.ts
99
167
  var toDocGroups = (options) => {
100
- const positions = new Map(options.navigation.map((name, index) => [name, index]));
101
- return [...Map.groupBy(options.docs, (doc) => doc.data.group)].toSorted((a, b) => (positions.get(a[0]) ?? options.navigation.length) - (positions.get(b[0]) ?? options.navigation.length) || a[0].localeCompare(b[0], locale_doc_group)).map(([name, docs]) => ({
102
- name,
103
- docs: docs.toSorted((a, b) => a.data.order - b.data.order || a.data.name.localeCompare(b.data.name, locale_doc_name) || a.id.localeCompare(b.id, locale_doc_name))
104
- }));
168
+ const positions = new Map(options.navigation.flatMap((path, index) => path.map((_name, depth) => [JSON.stringify(path.slice(0, depth + 1)), index])).toReversed());
169
+ return toDocGroupBranches({ docs: options.docs, path: [], positions, unlistedPosition: options.navigation.length });
105
170
  };
106
171
 
107
172
  // src/component/widget/shell/_wg-shell-nav.tsx
108
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
173
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
109
174
  var WgShellNav = (props) => {
110
175
  const docGroups = toDocGroups({ docs: props.docs, navigation: props.site.navigation });
111
- return /* @__PURE__ */ jsx2("nav", { className: clsx2("wg_shellNav__root"), "aria-label": copy_nav_aria_label, children: /* @__PURE__ */ jsx2("div", { className: clsx2("wg_shellNav__docs"), children: docGroups.map((group) => {
112
- const currentIndex = group.docs.findIndex((doc) => doc.id === props.current);
113
- return /* @__PURE__ */ jsxs2("section", { className: clsx2("wg_shellNav__docGroup"), "aria-label": group.name, children: [
114
- /* @__PURE__ */ jsx2("div", { className: clsx2("wg_shellNav__group"), children: group.name }),
115
- /* @__PURE__ */ jsx2("ul", { className: clsx2("wg_shellNav__list"), children: group.docs.map((doc, index) => /* @__PURE__ */ jsx2(
116
- "li",
117
- {
118
- className: clsx2("wg_shellNav__item", {
119
- "wg_shellNav__item--trail": index < currentIndex,
120
- "wg_shellNav__item--current": index === currentIndex
121
- }),
122
- children: /* @__PURE__ */ jsx2(
123
- "a",
124
- {
125
- className: clsx2("wg_shellNav__link", {
126
- "wg_shellNav__link--active": index === currentIndex
127
- }),
128
- href: `/${doc.id}/`,
129
- title: doc.data.label,
130
- "aria-current": index === currentIndex ? "page" : void 0,
131
- children: doc.data.name
132
- }
133
- )
134
- },
135
- doc.id
136
- )) })
137
- ] }, group.name);
138
- }) }) });
176
+ const currentDoc = props.docs.find((doc) => doc.id === props.current);
177
+ return /* @__PURE__ */ jsx3("nav", { className: clsx3("wg_shellNav__root"), "aria-label": copy_nav_aria_label, children: /* @__PURE__ */ jsx3("div", { className: clsx3("wg_shellNav__docs"), children: docGroups.map((group) => /* @__PURE__ */ jsxs3("section", { className: clsx3("wg_shellNav__docGroup"), "aria-label": group.name, children: [
178
+ /* @__PURE__ */ jsx3("div", { className: clsx3("wg_shellNav__group"), children: group.name }),
179
+ /* @__PURE__ */ jsx3(WgShellNavList, { group, currentDoc })
180
+ ] }, group.name)) }) });
139
181
  };
140
182
 
141
183
  // src/component/widget/shell/_wg-shell-toc.tsx
142
- import clsx3 from "clsx";
143
- import { Fragment } from "react";
144
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
184
+ import clsx4 from "clsx";
185
+ import { Fragment as Fragment2 } from "react";
186
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
145
187
  var WgShellToc = (props) => {
146
- return /* @__PURE__ */ jsxs3("nav", { className: clsx3("wg_shellToc__root"), "aria-label": copy_nav_toc_label, children: [
147
- /* @__PURE__ */ jsx3("div", { className: clsx3("wg_shellToc__label"), children: copy_nav_toc_label }),
148
- props.groups.map((group) => /* @__PURE__ */ jsxs3(Fragment, { children: [
149
- group.part !== void 0 && /* @__PURE__ */ jsx3("div", { className: clsx3("wg_shellToc__group"), children: group.part }),
150
- /* @__PURE__ */ jsx3("ul", { className: clsx3("wg_shellToc__list"), children: group.sections.map((section) => /* @__PURE__ */ jsxs3("li", { className: clsx3("wg_shellToc__item"), children: [
151
- /* @__PURE__ */ jsx3("a", { className: clsx3("wg_shellToc__link"), href: `#${section.heading.slug}`, "data-toc-link": "", children: section.heading.text }),
152
- section.subs.length > 0 && /* @__PURE__ */ jsx3("ul", { className: clsx3("wg_shellToc__sub"), children: section.subs.map((sub) => /* @__PURE__ */ jsx3("li", { className: clsx3("wg_shellToc__subItem"), children: /* @__PURE__ */ jsx3("a", { className: clsx3("wg_shellToc__subLink"), href: `#${sub.heading.slug}`, "data-toc-sub-link": "", children: sub.heading.text }) }, sub.heading.slug)) })
188
+ return /* @__PURE__ */ jsxs4("nav", { className: clsx4("wg_shellToc__root"), "aria-label": copy_nav_toc_label, children: [
189
+ /* @__PURE__ */ jsx4("div", { className: clsx4("wg_shellToc__label"), children: copy_nav_toc_label }),
190
+ props.groups.map((group) => /* @__PURE__ */ jsxs4(Fragment2, { children: [
191
+ group.part !== void 0 && /* @__PURE__ */ jsx4("div", { className: clsx4("wg_shellToc__group"), children: group.part }),
192
+ /* @__PURE__ */ jsx4("ul", { className: clsx4("wg_shellToc__list"), children: group.sections.map((section) => /* @__PURE__ */ jsxs4("li", { className: clsx4("wg_shellToc__item"), children: [
193
+ /* @__PURE__ */ jsx4("a", { className: clsx4("wg_shellToc__link"), href: `#${section.heading.slug}`, "data-toc-link": "", children: section.heading.text }),
194
+ section.subs.length > 0 && /* @__PURE__ */ jsx4("ul", { className: clsx4("wg_shellToc__sub"), children: section.subs.map((sub) => /* @__PURE__ */ jsx4("li", { className: clsx4("wg_shellToc__subItem"), children: /* @__PURE__ */ jsx4("a", { className: clsx4("wg_shellToc__subLink"), href: `#${sub.heading.slug}`, "data-toc-sub-link": "", children: sub.heading.text }) }, sub.heading.slug)) })
153
195
  ] }, section.heading.slug)) })
154
196
  ] }, group.sections[0].heading.slug))
155
197
  ] });
@@ -171,85 +213,85 @@ var asset_content_types = {
171
213
  var asset_content_type_default = "application/octet-stream";
172
214
 
173
215
  // src/component/widget/shell/wg-shell.tsx
174
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
216
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
175
217
  var reloadScript = `new EventSource(${JSON.stringify(asset_reload_path)}).onmessage=function(){location.reload()}`;
176
218
  var WgShell = (props) => {
177
219
  const tocGroups = props.outline === void 0 ? [] : toTocGroups(props.outline);
178
- return /* @__PURE__ */ jsxs4("html", { lang: "ko", children: [
179
- /* @__PURE__ */ jsxs4("head", { children: [
180
- /* @__PURE__ */ jsx4("meta", { charSet: "utf-8" }),
181
- /* @__PURE__ */ jsx4("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
182
- props.site.description !== void 0 && /* @__PURE__ */ jsx4("meta", { name: "description", content: props.site.description }),
183
- /* @__PURE__ */ jsx4("title", { children: props.title === void 0 ? props.site.title : `${props.title} \xB7 ${props.site.title}` }),
184
- /* @__PURE__ */ jsx4("link", { rel: "icon", href: asset_favicon_path, type: "image/svg+xml" }),
185
- /* @__PURE__ */ jsx4("style", { dangerouslySetInnerHTML: { __html: props.assets.fontCss } }),
186
- props.assets.preload.map((href) => /* @__PURE__ */ jsx4("link", { rel: "preload", href, as: "font", type: "font/woff2", crossOrigin: "" }, href)),
187
- /* @__PURE__ */ jsx4("script", { dangerouslySetInnerHTML: { __html: 'document.documentElement.style.scrollBehavior="auto"' } }),
188
- /* @__PURE__ */ jsx4("link", { rel: "stylesheet", href: asset_style_path }),
189
- props.assets.reload && /* @__PURE__ */ jsx4("script", { dangerouslySetInnerHTML: { __html: reloadScript } })
220
+ return /* @__PURE__ */ jsxs5("html", { lang: "ko", children: [
221
+ /* @__PURE__ */ jsxs5("head", { children: [
222
+ /* @__PURE__ */ jsx5("meta", { charSet: "utf-8" }),
223
+ /* @__PURE__ */ jsx5("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }),
224
+ props.site.description !== void 0 && /* @__PURE__ */ jsx5("meta", { name: "description", content: props.site.description }),
225
+ /* @__PURE__ */ jsx5("title", { children: props.title === void 0 ? props.site.title : `${props.title} \xB7 ${props.site.title}` }),
226
+ /* @__PURE__ */ jsx5("link", { rel: "icon", href: asset_favicon_path, type: "image/svg+xml" }),
227
+ /* @__PURE__ */ jsx5("style", { dangerouslySetInnerHTML: { __html: props.assets.fontCss } }),
228
+ props.assets.preload.map((href) => /* @__PURE__ */ jsx5("link", { rel: "preload", href, as: "font", type: "font/woff2", crossOrigin: "" }, href)),
229
+ /* @__PURE__ */ jsx5("script", { dangerouslySetInnerHTML: { __html: 'document.documentElement.style.scrollBehavior="auto"' } }),
230
+ /* @__PURE__ */ jsx5("link", { rel: "stylesheet", href: asset_style_path }),
231
+ props.assets.reload && /* @__PURE__ */ jsx5("script", { dangerouslySetInnerHTML: { __html: reloadScript } })
190
232
  ] }),
191
- /* @__PURE__ */ jsxs4("body", { className: clsx4("wg_shell__root"), children: [
192
- /* @__PURE__ */ jsxs4("div", { className: clsx4("wg_shell__sidebar"), children: [
193
- /* @__PURE__ */ jsxs4("div", { className: clsx4("wg_shell__brandRow"), children: [
194
- /* @__PURE__ */ jsx4("a", { className: clsx4("wg_shell__brand"), href: "/", "aria-current": props.current === void 0 ? "page" : void 0, children: props.site.title }),
195
- /* @__PURE__ */ jsx4(
233
+ /* @__PURE__ */ jsxs5("body", { className: clsx5("wg_shell__root"), children: [
234
+ /* @__PURE__ */ jsxs5("div", { className: clsx5("wg_shell__sidebar"), children: [
235
+ /* @__PURE__ */ jsxs5("div", { className: clsx5("wg_shell__brandRow"), children: [
236
+ /* @__PURE__ */ jsx5("a", { className: clsx5("wg_shell__brand"), href: "/", "aria-current": props.current === void 0 ? "page" : void 0, children: props.site.title }),
237
+ /* @__PURE__ */ jsx5(
196
238
  "button",
197
239
  {
198
- className: clsx4("wg_shell__menuToggle"),
240
+ className: clsx5("wg_shell__menuToggle"),
199
241
  type: "button",
200
242
  "aria-label": copy_nav_open,
201
243
  "aria-controls": "fh-navigation-drawer",
202
244
  "aria-expanded": "false",
203
245
  "data-navigation-open": "",
204
- children: /* @__PURE__ */ jsx4("svg", { className: clsx4("wg_shell__menuIcon"), viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx4("path", { d: "M4 6h16M4 12h16M4 18h16" }) })
246
+ children: /* @__PURE__ */ jsx5("svg", { className: clsx5("wg_shell__menuIcon"), viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx5("path", { d: "M4 6h16M4 12h16M4 18h16" }) })
205
247
  }
206
248
  )
207
249
  ] }),
208
- /* @__PURE__ */ jsxs4("div", { className: clsx4("wg_shell__navigation"), "data-navigation": "", children: [
209
- /* @__PURE__ */ jsx4(WgShellNav, { site: props.site, docs: props.docs, current: props.current }),
210
- tocGroups.length > 0 && /* @__PURE__ */ jsx4(WgShellToc, { groups: tocGroups })
250
+ /* @__PURE__ */ jsxs5("div", { className: clsx5("wg_shell__navigation"), "data-navigation": "", children: [
251
+ /* @__PURE__ */ jsx5(WgShellNav, { site: props.site, docs: props.docs, current: props.current }),
252
+ tocGroups.length > 0 && /* @__PURE__ */ jsx5(WgShellToc, { groups: tocGroups })
211
253
  ] })
212
254
  ] }),
213
- /* @__PURE__ */ jsx4("main", { className: clsx4("wg_shell__main"), children: props.children }),
214
- /* @__PURE__ */ jsxs4("dialog", { className: clsx4("wg_shell__drawer"), id: "fh-navigation-drawer", "aria-labelledby": "fh-navigation-title", "data-navigation-drawer": "", children: [
215
- /* @__PURE__ */ jsxs4("div", { className: clsx4("wg_shell__drawerHeader"), children: [
216
- /* @__PURE__ */ jsx4("h2", { className: clsx4("wg_shell__drawerTitle"), id: "fh-navigation-title", children: copy_nav_title }),
217
- /* @__PURE__ */ jsx4("button", { className: clsx4("wg_shell__drawerClose"), type: "button", "aria-label": copy_nav_close, "data-navigation-close": "", children: /* @__PURE__ */ jsx4("svg", { className: clsx4("wg_shell__menuIcon"), viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx4("path", { d: "m6 6 12 12M18 6 6 18" }) }) })
255
+ /* @__PURE__ */ jsx5("main", { className: clsx5("wg_shell__main"), children: props.children }),
256
+ /* @__PURE__ */ jsxs5("dialog", { className: clsx5("wg_shell__drawer"), id: "fh-navigation-drawer", "aria-labelledby": "fh-navigation-title", "data-navigation-drawer": "", children: [
257
+ /* @__PURE__ */ jsxs5("div", { className: clsx5("wg_shell__drawerHeader"), children: [
258
+ /* @__PURE__ */ jsx5("h2", { className: clsx5("wg_shell__drawerTitle"), id: "fh-navigation-title", children: copy_nav_title }),
259
+ /* @__PURE__ */ jsx5("button", { className: clsx5("wg_shell__drawerClose"), type: "button", "aria-label": copy_nav_close, "data-navigation-close": "", children: /* @__PURE__ */ jsx5("svg", { className: clsx5("wg_shell__menuIcon"), viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx5("path", { d: "m6 6 12 12M18 6 6 18" }) }) })
218
260
  ] }),
219
- /* @__PURE__ */ jsx4("div", { className: clsx4("wg_shell__drawerContent"), "data-navigation-content": "" })
261
+ /* @__PURE__ */ jsx5("div", { className: clsx5("wg_shell__drawerContent"), "data-navigation-content": "" })
220
262
  ] }),
221
- /* @__PURE__ */ jsx4("img", { className: clsx4("wg_shell__cursorFace"), src: asset_favicon_path, width: "24", height: "24", alt: "", "aria-hidden": "true", draggable: false, "data-cursor-face": "" }),
222
- /* @__PURE__ */ jsx4("script", { type: "module", src: asset_client_path })
263
+ /* @__PURE__ */ jsx5("img", { className: clsx5("wg_shell__cursorFace"), src: asset_favicon_path, width: "24", height: "24", alt: "", "aria-hidden": "true", draggable: false, "data-cursor-face": "" }),
264
+ /* @__PURE__ */ jsx5("script", { type: "module", src: asset_client_path })
223
265
  ] })
224
266
  ] });
225
267
  };
226
268
 
227
269
  // src/page/doc/pg-doc.tsx
228
- import { jsx as jsx5 } from "react/jsx-runtime";
270
+ import { jsx as jsx6 } from "react/jsx-runtime";
229
271
  var PgDoc = (props) => {
230
- return /* @__PURE__ */ jsx5(WgShell, { site: props.site, docs: props.docs, assets: props.assets, title: props.doc.data.name, current: props.doc.id, outline: props.doc.outline, children: /* @__PURE__ */ jsx5(WgProse, { html: props.doc.html }) });
272
+ return /* @__PURE__ */ jsx6(WgShell, { site: props.site, docs: props.docs, assets: props.assets, title: props.doc.data.name, current: props.doc.id, outline: props.doc.outline, children: /* @__PURE__ */ jsx6(WgProse, { html: props.doc.html }) });
231
273
  };
232
274
 
233
275
  // src/page/home/pg-home.tsx
234
- import { jsx as jsx6 } from "react/jsx-runtime";
276
+ import { jsx as jsx7 } from "react/jsx-runtime";
235
277
  var PgHome = (props) => {
236
- return /* @__PURE__ */ jsx6(WgShell, { site: props.site, docs: props.docs, assets: props.assets, outline: props.home?.outline, children: props.home === void 0 ? /* @__PURE__ */ jsx6(WgProse, { head: { title: props.site.title, lead: copy_home_missing } }) : /* @__PURE__ */ jsx6(WgProse, { html: props.home.html }) });
278
+ return /* @__PURE__ */ jsx7(WgShell, { site: props.site, docs: props.docs, assets: props.assets, outline: props.home?.outline, children: props.home === void 0 ? /* @__PURE__ */ jsx7(WgProse, { head: { title: props.site.title, lead: copy_home_missing } }) : /* @__PURE__ */ jsx7(WgProse, { html: props.home.html }) });
237
279
  };
238
280
 
239
281
  // src/app.tsx
240
- import { jsx as jsx7 } from "react/jsx-runtime";
282
+ import { jsx as jsx8 } from "react/jsx-runtime";
241
283
  var toHtml = (page) => {
242
284
  return `<!DOCTYPE html>${renderToStaticMarkup(page)}`;
243
285
  };
244
286
  var createApp = (options) => {
245
287
  const app = new Hono();
246
- app.get("/", (c) => c.html(toHtml(/* @__PURE__ */ jsx7(PgHome, { site: options.site, docs: options.store.docs, assets: options.assets, home: options.store.home }))));
288
+ app.get("/", (c) => c.html(toHtml(/* @__PURE__ */ jsx8(PgHome, { site: options.site, docs: options.store.docs, assets: options.assets, home: options.store.home }))));
247
289
  app.get(
248
290
  "/:slug{.+}/",
249
291
  ssgParams(() => options.store.docs.map((doc) => ({ slug: doc.id }))),
250
292
  (c) => {
251
293
  const doc = options.store.docs.find((entry) => entry.id === c.req.param("slug"));
252
- return doc === void 0 ? c.notFound() : c.html(toHtml(/* @__PURE__ */ jsx7(PgDoc, { site: options.site, docs: options.store.docs, assets: options.assets, doc })));
294
+ return doc === void 0 ? c.notFound() : c.html(toHtml(/* @__PURE__ */ jsx8(PgDoc, { site: options.site, docs: options.store.docs, assets: options.assets, doc })));
253
295
  }
254
296
  );
255
297
  return app;
@@ -935,7 +977,7 @@ import { VFile } from "vfile";
935
977
  import { parse as parseYaml } from "yaml";
936
978
 
937
979
  // src/type/doc-data.ts
938
- import { z } from "zod";
980
+ import { z as z2 } from "zod";
939
981
 
940
982
  // src/constant/doc.ts
941
983
  var doc_type = {
@@ -944,28 +986,32 @@ var doc_type = {
944
986
  };
945
987
  var doc_order_default = Number.MAX_SAFE_INTEGER;
946
988
 
989
+ // src/type/group-path.ts
990
+ import { z } from "zod";
991
+ var groupPathSchema = z.union([z.string().trim().min(1), z.array(z.string().trim().min(1)).min(1)]).transform((path) => typeof path === "string" ? [path] : path);
992
+
947
993
  // src/type/doc-data.ts
948
- var docDataSchema = z.object({
994
+ var docDataSchema = z2.object({
949
995
  /**
950
996
  * 문서 이름 · 제목·목록 · 같은 첫 글자 허용
951
997
  */
952
- name: z.string().trim().min(1),
998
+ name: z2.string().trim().min(1),
953
999
  /**
954
1000
  * 짧은 문서 설명 · 사이드바 툴팁
955
1001
  */
956
- label: z.string(),
1002
+ label: z2.string(),
957
1003
  /**
958
1004
  * 문서 종류 · blueprint 전용 표현 미구현
959
1005
  */
960
- type: z.enum(doc_type).default(doc_type.document),
1006
+ type: z2.enum(doc_type).default(doc_type.document),
961
1007
  /**
962
- * 사이드바의 문서 묶음
1008
+ * 사이드바의 문서 묶음 경로 · 문자열도 배열로 정규화
963
1009
  */
964
- group: z.string().trim().min(1),
1010
+ group: groupPathSchema,
965
1011
  /**
966
1012
  * 묶음 안의 읽는 순서 · 미지정 문서는 뒤에서 이름순
967
1013
  */
968
- order: z.number().int().nonnegative().default(doc_order_default)
1014
+ order: z2.number().int().nonnegative().default(doc_order_default)
969
1015
  });
970
1016
 
971
1017
  // src/content/read-docs.ts
@@ -1068,7 +1114,7 @@ var createDevApp = (options) => {
1068
1114
  };
1069
1115
 
1070
1116
  // src/type/site-config.ts
1071
- import { z as z2 } from "zod";
1117
+ import { z as z3 } from "zod";
1072
1118
 
1073
1119
  // src/constant/status.ts
1074
1120
  var status_kind = {
@@ -1081,36 +1127,36 @@ var status_default_phrases = [
1081
1127
  ];
1082
1128
 
1083
1129
  // src/type/site-config.ts
1084
- var siteConfigSchema = z2.object({
1130
+ var siteConfigSchema = z3.object({
1085
1131
  /**
1086
1132
  * 사이드바 이름과 탭 제목
1087
1133
  */
1088
- title: z2.string().default(copy_site_title_default),
1134
+ title: z3.string().default(copy_site_title_default),
1089
1135
  /**
1090
1136
  * HTML 설명 메타데이터 · 선택
1091
1137
  */
1092
- description: z2.string().optional(),
1138
+ description: z3.string().optional(),
1093
1139
  /**
1094
- * 탐색 묶음의 읽는 순서 · 미지정 묶음은 뒤에서 이름순
1140
+ * 탐색 묶음 경로의 읽는 순서 · 미지정 형제 묶음은 뒤에서 이름순
1095
1141
  */
1096
- navigation: z2.array(z2.string().trim().min(1)).refine((groups) => new Set(groups).size === groups.length, copy_error_navigation_clash).default([...site_navigation_default]),
1142
+ navigation: z3.array(groupPathSchema).refine((groups) => new Set(groups.map((path) => JSON.stringify(path))).size === groups.length, copy_error_navigation_clash).default(site_navigation_default.map((name) => [name])),
1097
1143
  /**
1098
1144
  * 본문에서 상태 표지로 표시할 문구
1099
1145
  */
1100
- status: z2.array(
1101
- z2.object({
1146
+ status: z3.array(
1147
+ z3.object({
1102
1148
  /**
1103
1149
  * 찾을 문구 원문
1104
1150
  */
1105
- phrase: z2.string().min(1).refine((phrase) => phrase.trim().length > 0),
1151
+ phrase: z3.string().min(1).refine((phrase) => phrase.trim().length > 0),
1106
1152
  /**
1107
1153
  * 상태 표지의 색 종류
1108
1154
  */
1109
- kind: z2.enum(status_kind),
1155
+ kind: z3.enum(status_kind),
1110
1156
  /**
1111
1157
  * 문구 뒤의 YYYY-MM-DD 날짜 포함 여부
1112
1158
  */
1113
- date: z2.boolean().optional()
1159
+ date: z3.boolean().optional()
1114
1160
  })
1115
1161
  ).default(status_default_phrases)
1116
1162
  });