@hutusi/amytis 1.15.0 → 1.17.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 (120) hide show
  1. package/.claude/rules/immersive-reading.md +21 -0
  2. package/.claude/rules/rst.md +13 -0
  3. package/CHANGELOG.md +42 -0
  4. package/CLAUDE.md +89 -219
  5. package/bun.lock +185 -547
  6. package/content/books/sample-book/index.mdx +3 -0
  7. package/content/posts/code-block-features-showcase.mdx +223 -0
  8. package/docs/ALERTS.md +112 -0
  9. package/docs/ARCHITECTURE.md +298 -5
  10. package/docs/CODE-BLOCKS.md +238 -0
  11. package/docs/CONTRIBUTING.md +25 -0
  12. package/docs/DIGITAL_GARDEN.md +1 -1
  13. package/docs/guides/README.md +11 -0
  14. package/docs/guides/importing-vuepress-books.md +237 -0
  15. package/eslint.config.mjs +18 -6
  16. package/package.json +42 -20
  17. package/scripts/generate-code-group-icons.ts +79 -0
  18. package/scripts/render-rst.py +207 -3
  19. package/scripts/sync-vuepress-book.ts +710 -0
  20. package/site.config.example.ts +3 -3
  21. package/site.config.ts +3 -3
  22. package/src/app/[slug]/layout.tsx +30 -0
  23. package/src/app/books/[slug]/{[chapter] → [...chapter]}/page.tsx +32 -10
  24. package/src/app/books/[slug]/layout.tsx +24 -0
  25. package/src/app/books/[slug]/page.tsx +85 -34
  26. package/src/app/globals.css +570 -123
  27. package/src/app/page.tsx +7 -1
  28. package/src/app/posts/layout.tsx +20 -0
  29. package/src/app/series/[slug]/page.tsx +33 -9
  30. package/src/app/sitemap.ts +3 -3
  31. package/src/components/ArticleCopyCleaner.tsx +64 -0
  32. package/src/components/BookMobileNav.tsx +44 -50
  33. package/src/components/BookReadingShell.tsx +145 -0
  34. package/src/components/BookSidebar.tsx +0 -0
  35. package/src/components/CodeBlock.test.tsx +93 -8
  36. package/src/components/CodeBlock.tsx +39 -101
  37. package/src/components/CodeBlockToolbar.tsx +88 -0
  38. package/src/components/CodeGroup.tsx +81 -0
  39. package/src/components/CoverImage.tsx +1 -0
  40. package/src/components/CuratedSeriesSection.tsx +28 -10
  41. package/src/components/ExternalLinkIcon.tsx +15 -0
  42. package/src/components/FeaturedStoriesSection.tsx +44 -23
  43. package/src/components/Footer.tsx +1 -1
  44. package/src/components/GithubAlert.tsx +97 -0
  45. package/src/components/ImmersiveReader.tsx +130 -0
  46. package/src/components/ImmersiveReaderTopBar.tsx +106 -0
  47. package/src/components/ImmersiveReadingFlagHandler.tsx +40 -0
  48. package/src/components/ImmersiveReadingPrefsPopover.tsx +249 -0
  49. package/src/components/ImmersiveReadingProvider.tsx +168 -0
  50. package/src/components/ImmersiveSeriesSidebar.tsx +143 -0
  51. package/src/components/ImmersiveToggleButton.tsx +45 -0
  52. package/src/components/MarkdownRenderer.test.tsx +14 -4
  53. package/src/components/MarkdownRenderer.tsx +175 -23
  54. package/src/components/Mermaid.tsx +32 -1
  55. package/src/components/Navbar.tsx +3 -1
  56. package/src/components/PostList.tsx +1 -1
  57. package/src/components/PostNavigation.tsx +13 -2
  58. package/src/components/PostReadingShell.tsx +68 -0
  59. package/src/components/PostSidebar.tsx +13 -2
  60. package/src/components/ReadingProgressBar.tsx +1 -1
  61. package/src/components/RstRenderer.test.tsx +15 -15
  62. package/src/components/RstRenderer.tsx +37 -2
  63. package/src/components/Search.tsx +18 -4
  64. package/src/components/SelectedBooksSection.tsx +27 -8
  65. package/src/components/SeriesCatalog.tsx +1 -1
  66. package/src/components/ShareBar.tsx +5 -0
  67. package/src/components/TocPanel.tsx +10 -2
  68. package/src/hooks/useActiveHeading.ts +35 -13
  69. package/src/hooks/useSidebarAutoScroll.ts +31 -7
  70. package/src/i18n/translations.ts +44 -0
  71. package/src/layouts/BookLayout.tsx +62 -74
  72. package/src/layouts/PostLayout.tsx +154 -111
  73. package/src/lib/code-group-icons.test.ts +78 -0
  74. package/src/lib/code-group-icons.ts +148 -0
  75. package/src/lib/immersive-reading-prefs.ts +104 -0
  76. package/src/lib/markdown.test.ts +56 -13
  77. package/src/lib/markdown.ts +217 -57
  78. package/src/lib/normalize-vuepress-math.ts +118 -0
  79. package/src/lib/rehype-fence-meta.ts +22 -0
  80. package/src/lib/remark-book-chapter-links.ts +106 -0
  81. package/src/lib/remark-code-group.ts +54 -0
  82. package/src/lib/remark-github-alerts.test.ts +83 -0
  83. package/src/lib/remark-github-alerts.ts +65 -0
  84. package/src/lib/remark-vuepress-containers.ts +130 -0
  85. package/src/lib/rst-renderer.ts +19 -7
  86. package/src/lib/rst.test.ts +212 -2
  87. package/src/lib/rst.ts +217 -13
  88. package/src/lib/scroll-utils.ts +44 -6
  89. package/src/lib/shiki-rst.ts +185 -0
  90. package/src/lib/shiki.test.ts +153 -0
  91. package/src/lib/shiki.ts +292 -0
  92. package/src/lib/shuffle.ts +15 -1
  93. package/src/lib/sort.ts +15 -0
  94. package/src/lib/urls.ts +62 -0
  95. package/src/test-utils/render.ts +23 -0
  96. package/tests/fixtures/sync-vuepress-book/docs/.vuepress/config.js +43 -0
  97. package/tests/fixtures/sync-vuepress-book/docs/intro/welcome.md +7 -0
  98. package/tests/fixtures/sync-vuepress-book/docs/maths/linear/assets/diagram.png +1 -0
  99. package/tests/fixtures/sync-vuepress-book/docs/maths/linear/matrices.md +7 -0
  100. package/tests/fixtures/sync-vuepress-book/docs/maths/linear/vectors.md +9 -0
  101. package/tests/helpers/env.ts +19 -0
  102. package/tests/integration/book-chapter-links.test.ts +107 -0
  103. package/tests/integration/book-index-cta.test.ts +87 -0
  104. package/tests/integration/books-nested-toc.test.ts +176 -0
  105. package/tests/integration/books.test.ts +3 -2
  106. package/tests/integration/code-block-features.test.ts +188 -0
  107. package/tests/integration/code-group.test.ts +183 -0
  108. package/tests/integration/code-notation.test.ts +97 -0
  109. package/tests/integration/github-alerts.test.ts +82 -0
  110. package/tests/integration/markdown-external-links.test.ts +103 -0
  111. package/tests/integration/normalize-vuepress-math.test.ts +149 -0
  112. package/tests/integration/reading-time-headings.test.ts +8 -6
  113. package/tests/integration/series-draft.test.ts +6 -13
  114. package/tests/integration/series-index-cta.test.ts +88 -0
  115. package/tests/integration/sync-vuepress-book.test.ts +443 -0
  116. package/tests/integration/vuepress-containers.test.ts +107 -0
  117. package/tests/tooling/new-post.test.ts +1 -1
  118. package/tests/unit/immersive-reading-prefs.test.ts +144 -0
  119. package/tests/unit/static-params.test.ts +32 -19
  120. package/vercel.json +7 -0
@@ -0,0 +1,21 @@
1
+ ---
2
+ paths:
3
+ - "src/components/Immersive*.tsx"
4
+ - "src/components/PostReadingShell.tsx"
5
+ - "src/components/BookReadingShell.tsx"
6
+ - "src/components/Navbar.tsx"
7
+ - "src/components/Footer.tsx"
8
+ - "src/components/ReadingProgressBar.tsx"
9
+ - "src/lib/immersive-reading-prefs.ts"
10
+ - "src/app/books/**"
11
+ - "src/app/[slug]/**"
12
+ - "src/app/posts/**"
13
+ - "src/app/globals.css"
14
+ ---
15
+
16
+ # Immersive reading gotchas
17
+
18
+ - **Chrome-hiding hooks.** `Navbar`, `Footer`, and `ReadingProgressBar` carry stable `data-site-nav` / `data-site-footer` / `data-reading-progress` attributes that the CSS rules in `globals.css` use to hide chrome when `html[data-immersive="true"]` is set. Don't strip these attributes during refactors — the fullscreen `ImmersiveReader` overlay in books and series posts depends on them as defense-in-depth even though the overlay also covers them. Reading-theme overrides (Light / Sepia / Dark) are scoped to `[data-reader-overlay]`, not `<html>`, so they compose with the site's light/dark theme without leaking outside the overlay; the overlay also adds Tailwind's `.dark` class when `readingTheme === 'dark'` so `dark:prose-invert` fires inside it even when the site is in light mode.
19
+ - **Provider is mounted at three layout boundaries**, one per content surface that supports the reader: `src/app/books/[slug]/layout.tsx` (chapter routes), `src/app/[slug]/layout.tsx` (series posts on autoPaths URLs, the default), and `src/app/posts/layout.tsx` (series posts on default-path URLs). Each layout mounts an independent provider instance — `enabled` state doesn't bleed between content types — but they all share the same localStorage key (`amytis-reader-prefs`), so a reader's font/theme/width prefs carry across books and series. Don't merge the three or move the provider to root layout: doing so would leak `enabled=true` to pages without a shell (PostReadingShell / BookReadingShell) to render the overlay, breaking the page.
20
+ - **`ImmersiveReadingFlagHandler` must stay in its own `<Suspense>` boundary in each of those three layouts**, as a sibling of `{children}` (not inside the provider), because its `useSearchParams` triggers a static-export bailout — wrapping the provider would drag the chapter/post page out of static prerender. The handler must **not** use a one-shot ref guard either: caught in PR-#93 review, the ref survives client-side navigation under the persistent layout, so a second `?immersive=1` click in the same tab silently no-ops. Rely on `router.replace` stripping the flag (which re-fires the effect with the flag gone) instead.
21
+ - **Prefs persistence quirks** (`src/components/ImmersiveReadingProvider.tsx` + `src/lib/immersive-reading-prefs.ts`): persist effect flips `hydratedRef` on its *first run* and returns — moving the flip into the hydration effect causes a default-clobbering race (the persist effect runs before React commits the stored values from the closure). Per-key defensive parsing on read is also load-bearing: a corrupt single value (schema drift, hand-edits) must fall back to default without discarding the whole blob. Both behaviours have unit/integration test coverage; don't regress them when refactoring the storage layer or the hydrate/persist effects.
@@ -0,0 +1,13 @@
1
+ ---
2
+ paths:
3
+ - "src/lib/rst*.ts"
4
+ - "src/lib/shiki-rst.ts"
5
+ - "src/components/RstRenderer.tsx"
6
+ ---
7
+
8
+ # rST rendering gotchas
9
+
10
+ - **rST needs Python `docutils`.** Set `AMYTIS_RST_PYTHON=/path/to/python` if not on `$PATH`. Without it, falls back to a lower-fidelity built-in parser.
11
+ - **sanitize-html allowlist must keep `style` + `data-*` on `pre`/`code`/`span`/`div`.** Stripping any of these silently kills Shiki output (monochrome text in prod, looks fine locally because dev rST isn't sanitized). See `src/components/RstRenderer.tsx`.
12
+ - **Code-group tabs add `<input type="radio">` + `<label>` to the sanitize-html allowlist.** Keep the `transformTags` guard in `RstRenderer.tsx` that strips any `<input>` whose `type !== "radio"` — that's the defense against an rST author injecting password/file/etc. inputs through raw HTML.
13
+ - **Bump `RST_RENDERER_DISK_CACHE_VERSION` (`src/lib/rst-renderer.ts`) on highlighter-output changes.** Stale on-disk caches in `.cache/rst-renderer/` will serve old markup otherwise. Run `rm -rf .cache/rst-renderer` after pulling such a change.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,48 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.17.0] - 2026-06-11
9
+
10
+ ### Added
11
+ - **Immersive Reading Mode**: A fullscreen, distraction-free reader for book chapters and series posts, launched from new "Immersive reading" CTAs on book and series index pages (deep-linkable via `?immersive=1`). Includes an `Aa` preferences popover (font size, Light / Sepia / Dark reading theme, column width) persisted in `localStorage` with a reset button, a reading progress bar under the top bar, and dedicated sidebars — the book sidebar with collapsible sections, plus a new series sidebar that inlines the active post's table of contents.
12
+ - **Homepage Section Ordering**: The curated homepage sections (`featured-posts`, `featured-series`, `featured-books`) accept an optional per-section `order` field — `shuffle`, `date-desc`, or `date-asc`.
13
+ - **VuePress Importer Upgrades**: `bun run sync-vuepress-book` understands VuePress 1.x sidebar shapes, gains `--skip-common` / `--skip` flags, supports narrow `chapters:`-only re-syncs, and falls back to `README.md` / `README.mdx` when resolving chapter ids.
14
+
15
+ ### Changed
16
+ - **Homepage shuffle** now reshuffles on every visit instead of rotating daily.
17
+ - **Vercel builds** are pinned via `vercel.json` and install with `--frozen-lockfile`.
18
+
19
+ ### Fixed
20
+ - **Immersive Reader Robustness**: Active-heading tracking and anchor scrolling now target the overlay's scroll container; the overlay is preserved when navigating across a collection; sidebar scroll position survives chapter/post clicks; the reading-theme override reliably flips the theme; the `Aa` popover stacks above code blocks; the chrome-hiding CSS is no longer stripped by Lightning CSS; and the first persist run no longer clobbers stored preferences.
21
+ - **Shuffle PRNG**: Small seeds are decorrelated with a splitmix32 finalizer and guarded against a zero state locking xorshift32; the featured-posts shuffle button is hidden when every slot is pinned.
22
+ - **Book Chapter Layout**: Chapter column width aligned with the site navbar and the article centered in its column; React warnings from VuePress-imported tags are silenced.
23
+
24
+ ## [1.16.0] - 2026-06-01
25
+
26
+ ### Added
27
+ - **VuePress Book Pipeline**: Books are now first-class with nested chapter ids and TOCs, a catch-all chapter route, collapsible sections in the chapter sidebar, book-level LaTeX/math, VuePress `:::container` admonitions, and inter-chapter links. Includes a new `bun run sync-vuepress-book` importer that handles frontmatter-less chapters and stale `.md` references gracefully.
28
+ - **Shiki Syntax Highlighting**: Replaced `react-syntax-highlighter`/Prism with a build-time Shiki pipeline across both Markdown/MDX and rST. Unknown fence languages now degrade to plaintext with a build-log warning instead of crashing the build, and any bundled Shiki language is lazy-loaded on demand.
29
+ - **Code-Group Tabs**: New `:::code-group` (Markdown) and `.. code-group::` (rST) directives render fenced blocks as switchable tabs, with auto-detected icons for languages, package managers, and config files.
30
+ - **Shiki Notation Comments**: Inline `// [!code focus]`, `[!code error]`, `[!code warning]`, `[!code highlight]`, and diff markers render with visual indicators inside code blocks.
31
+ - **GitHub-Flavored Alert Blockquotes**: `> [!NOTE]`, `> [!TIP]`, `> [!IMPORTANT]`, `> [!WARNING]`, `> [!CAUTION]` render as styled callouts in both Markdown and rST (admonition visual parity).
32
+ - **Reading Meta**: Word count appears alongside reading time on post and chapter headers, with Chinese localization (`5 分钟阅读`).
33
+ - **External Link Affordance**: Outward-arrow icon on external links, opened in a new tab.
34
+ - **Code Block Polish**: Real tab icons via Iconify for code-group headers, proper-case language labels, a more prominent highlighted-line background, and rST `:linenos:` / `:emphasize-lines:` / `:caption:` support.
35
+
36
+ ### Changed
37
+ - **Mermaid Diagrams** now render frameless by default — no outer border, padding, background, or shadow. Diagram SVGs use the full column width.
38
+ - **`showChapterExcerpt`** defaults to `false` on books; opt back in per-book.
39
+ - **Markdown Rendering Fidelity**: Single-line `$$ x $$` block math now expands and parses as display math; block math centers via `.katex-display`; VuePress inline `<img>` styling is preserved through import; KaTeX `unicodeTextInMathMode` warnings (noise on CJK math labels) are scoped-silenced.
40
+ - **Dependency Refresh**: TypeScript 5.9 → 6.0, ESLint 9 → 10 (config rewritten without `eslint-config-next`), Next.js + safe minor/patch sweep, `@types/node` 24 → 25, `pdf-to-img` 5 → 6.
41
+
42
+ ### Fixed
43
+ - **Hydration Mismatches**: Locale-bound text in `TocPanel`, `PostNavigation`, `PostSidebar`, and `ShareBar` no longer logs a hydration error on first paint for users whose stored locale differs from `siteConfig.i18n.defaultLocale`. Mermaid's mutation-prone wrapper is likewise marked.
44
+ - **Table Padding**: Restored horizontal padding on prose table cells (the earlier `:where()`-based override was silently stripped by Tailwind v4 / Lightning CSS; replaced with a plain descendant selector that survives compilation).
45
+ - **rST Fallback Parser**: Added rendering for `.. figure::`, line blocks, admonitions (including custom `.. cnote::` with caption), `:doc:` / `:ref:` / `:numref:` roles; suppresses `.. toctree::` from rendered body; normalizes escaped whitespace in inline rST.
46
+ - **React 19 Warnings**: Resolved `react-hooks/set-state-in-effect` warnings and silenced image-preload warnings for local images. Eager `CoverImage` variants are no longer deprioritized.
47
+ - **Copy-Paste UX**: Stripped per-paragraph backgrounds on article copy to prevent "striping" when pasting into rich-text editors. Clipboard-API guard before `preventDefault`.
48
+ - **Misc**: `<summary>` styled as an interactive link; code-group icon lookups via `Object.hasOwn`; shiki dedup state reset in `finally`; `<style>` element scoping for shiki output; book import edge cases (URI decode, Vue component warnings, fenced blocks in container normalizer, chapter image paths relative to parent dir).
49
+
8
50
  ## [1.15.0] - 2026-04-12
9
51
 
10
52
  ### Added
package/CLAUDE.md CHANGED
@@ -1,231 +1,101 @@
1
1
  # CLAUDE.md
2
2
 
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3
+ Guidance for Claude Code in this repo. Rules, gotchas, and pointers not reference material.
4
4
 
5
- ## Project Overview
5
+ ## Project
6
6
 
7
- Amytis is a static "digital garden" blog built with Next.js 15+ (App Router), React 19, and Tailwind CSS v4. Content is authored in MDX/Markdown files and statically generated at build time. Features include series support, multi-language (i18n), configurable themes, and comments integration.
7
+ Amytis: static digital-garden blog (Next.js 16 App Router, React 19, Tailwind v4, static export). Content in MDX / Markdown (primary), rST (legacy support); everything resolves at build time. Package manager: bun.
8
8
 
9
- ## Commands
9
+ ## Essential commands
10
10
 
11
11
  ```bash
12
- # Development
13
- bun dev # Start dev server at localhost:3000
14
- bun run lint # Run ESLint
15
-
16
- # Testing
17
- bun test # Run all tests
18
- bun run test:unit # Run unit tests (src/)
19
- bun run test:int # Run integration tests
20
- bun run test:e2e # Run end-to-end tests
21
- bun run test:mobile # Run Playwright mobile compatibility tests (requires dev server)
22
- bun test path/to/file.test.ts # Run a single test file
23
-
24
- # Build
25
- bun run build # Full production build (copies assets, builds Next.js, optimizes images)
26
- bun run build:dev # Development build (no image optimization, faster) — also regenerates Pagefind search index in public/pagefind/
27
- bun run clean # Remove .next, out, public/posts directories
28
-
29
- # Deploy
30
- bun run deploy # Deploy out/ to Linux/nginx server via rsync+sshpass (reads .env.local)
31
-
32
- # Content creation
33
- bun run new "Post Title" # Create new post
34
- bun run new-series "Series Name" # Create new series
35
- bun run new-from-pdf doc.pdf # Create post from PDF
36
- bun run new-from-images ./photos # Create post from image folder
37
- bun run new-flow # Create today's flow note
38
- bun run add-series-redirects # Add redirectFrom to all series posts (for autoPaths migration)
39
- bun run add-series-redirects <series-slug> # Add redirectFrom to one series only
40
- bun run add-series-redirects --dry-run # Preview without writing
41
- bun run new-flow-from-chat # Import all new files from imports/chats/
42
- bun run sync-book # Sync chapters list for all books from disk
43
- bun run sync-book <slug> # Sync chapters list for one book
12
+ bun dev # dev server (Turbopack, http://localhost:3000)
13
+ bun run lint # ESLint
14
+ bun test # all tests
15
+ bun run build # production build (image optimization + Pagefind)
16
+ bun run build:dev # faster build; regenerates public/pagefind/ for search-in-dev
17
+ bun run clean # nuke .next, out, public/posts when caches misbehave
44
18
  ```
45
19
 
46
- ## Design Principles
47
-
48
- - **Strict build over silent runtime failure.** This is a statically exported site — all misconfiguration must be caught at build time. Prefer `throw` in `generateStaticParams` and similar build-time functions over silent skips or `console.warn`. Examples: `validateSeriesAutoPaths` throws on slug collisions; `redirectFrom` alias conflicts should throw rather than silently producing broken redirects.
49
-
50
- ## Architecture
51
-
52
- ### Data Flow
53
-
54
- 1. **Content source**: MDX/Markdown files in `content/posts/` and `content/series/`
55
- 2. **Data layer**: `src/lib/markdown.ts` - reads files with Node `fs`, parses frontmatter with `gray-matter`, validates with Zod
56
- 3. **Static generation**: Routes use `generateStaticParams` to pre-render at build time
57
- 4. **Rendering**: `react-markdown` with remark/rehype plugins for GFM, math (KaTeX), syntax highlighting, and Mermaid diagrams
58
-
59
- ### Key Files
60
-
61
- - `site.config.ts` - Site configuration (nav, social, pagination, themes, i18n, analytics, comments)
62
- - `src/lib/markdown.ts` - Data access layer with all content query functions
63
- - `src/lib/urls.ts` - Central URL helpers (`getPostUrl`, `getPostsBasePath`, `getSeriesCustomPaths`, etc.) — always use these instead of hardcoding `/posts/[slug]`
64
- - `src/lib/search-utils.ts` - Pure search utilities (URL type detection, date extraction, title cleaning, markdown stripping) shared by `Search` and the search index route
65
- - `src/app/globals.css` - Theme CSS variables and color palettes
66
- - `src/components/MarkdownRenderer.tsx` - MDX rendering with all plugins
67
- - `src/i18n/translations.ts` - Language strings for i18n
68
-
69
- ### Route Structure
70
-
71
- - `/` - Homepage with hero, featured series, featured posts, latest writing
72
- - `/posts/[slug]` - Individual post pages
73
- - `/posts/page/[page]` - Paginated post listing
74
- - `/series` - All series overview
75
- - `/series/[slug]` - Single series with its posts
76
- - `/series/[slug]/page/[page]` - Series pagination
77
- - `/tags` - Tag cloud with post counts
78
- - `/tags/[tag]` - Posts filtered by tag
79
- - `/authors/[author]` - Posts filtered by author
80
- - `/archive` - Chronological listing grouped by year/month
81
- - `/books` - All books overview
82
- - `/books/[slug]` - Single book with chapter listing
83
- - `/books/[slug]/[chapter]` - Individual chapter page
84
- - `/flows` - Flow notes listing (paginated)
85
- - `/flows/page/[page]` - Paginated flow listing
86
- - `/flows/[year]` - Flows filtered by year
87
- - `/flows/[year]/[month]` - Flows filtered by month
88
- - `/flows/[year]/[month]/[day]` - Single flow detail page
89
- - `/[slug]` - Static pages (about, subscribe, etc.) and custom posts/series listing pages
90
- - `/[prefix]/[slug]` - Posts at custom URL prefixes (e.g. `/articles/my-post`, `/weeklies/my-post`)
91
- - `/[prefix]/page/[page]` - Paginated listing at custom URL prefixes
92
-
93
- ### Content Structure
94
-
95
- **Posts** support two formats:
96
- - **Flat file**: `content/posts/my-post.mdx`
97
- - **Nested folder**: `content/posts/my-post/index.mdx` (allows co-located images in `./images/`)
98
-
99
- **Series** live in `content/series/[slug]/index.mdx` with optional `images/` folder for cover images.
100
-
101
- **Books** live in `content/books/[slug]/` with `index.mdx` for metadata and separate `.mdx` files per chapter. The `index.mdx` frontmatter defines chapter ordering with an optional parts structure.
102
-
103
- **Flows** (daily notes) live in `content/flows/YYYY/MM/DD.md` (or `.mdx`). Each flow has a date, title, excerpt, tags, and markdown content.
104
-
105
- Date-prefixed filenames (`2026-01-01-my-post.mdx`) extract dates automatically.
106
-
107
- ## Config Files
108
-
109
- There are two config files that must be kept in sync:
110
-
111
- - **`site.config.ts`** — the live config for this repo (i18n enabled; locale-aware fields use `{ en: '...', zh: '...' }` objects)
112
- - **`site.config.example.ts`** — single-language starter template shipped via `create-amytis`; locale-aware fields use plain strings; optional features (books, flow) default to disabled
113
-
114
- **Rule:** Any schema change to `site.config.ts` (new field, renamed field, type change) must be mirrored in `site.config.example.ts`. Locale-aware values (`string | Record<string, string>`) should use plain strings in the example.
115
-
116
- ## Configuration (`site.config.ts`)
117
-
118
- Key configuration options:
119
- - `nav` - Navigation links with weights
120
- - `social` - GitHub, Twitter, email links for footer
121
- - `series.navbar` - Series slugs to show in navbar dropdown
122
- - `series.customPaths` - Per-series custom URL prefix e.g. `{ 'weeklies': 'weeklies' }` → posts at `/weeklies/[slug]`
123
- - `pagination.posts`, `pagination.series` - Items per page
124
- - `themeColor` - 'default' | 'blue' | 'rose' | 'amber'
125
- - `hero` - Homepage hero title and subtitle
126
- - `i18n` - Default locale and supported locales
127
- - `featured.series` - Scrollable series: `scrollThreshold` (default: 2), `maxItems` (default: 6)
128
- - `featured.stories` - Scrollable stories: `scrollThreshold` (default: 1), `maxItems` (default: 5)
129
- - `analytics.providers` - array of enabled providers: `['umami', 'google']`; `[]` disables analytics
130
- - `comments.provider` - 'giscus' | 'disqus' | null
131
- - `feed.format` - 'rss' | 'atom' | 'both'
132
- - `feed.content` - 'full' | 'excerpt'
133
- - `feed.maxItems` - max feed items (0 = unlimited)
134
- - `footer.bottomLinks` - custom links in the footer bottom bar (ICP, cookie policy, etc.); `text` accepts plain string or `{ en, zh }` locale map
135
- - `posts.basePath` - Custom URL prefix for all posts (default: `'posts'`); e.g. `'articles'` → posts at `/articles/[slug]`
136
- - `posts.authors.default` - Fallback authors when a post has none set in frontmatter
137
- - `posts.authors.showInHeader` - Show author byline below post title (default: true)
138
- - `posts.authors.showAuthorCard` - Show author card at end of post (default: true)
139
- - `posts.excludeFromListing` - Series slugs whose posts are hidden from `/posts` listing pages
140
- - `authors` - Per-author profiles: `bio`, `avatar` (image path), `social` (array of `{ image, description }`)
141
-
142
- ## Content Frontmatter
143
-
144
- ### Posts
145
-
146
- ```yaml
147
- ---
148
- title: "Post Title"
149
- subtitle: "Optional subtitle line" # Rendered below the title in italic
150
- date: "2026-01-01"
151
- excerpt: "Optional summary (auto-generated if omitted)"
152
- category: "Category Name"
153
- tags: ["tag1", "tag2"]
154
- authors: ["Author Name"]
155
- series: "series-slug" # Link to a series
156
- draft: true # Hidden in production
157
- featured: true # Show in featured section
158
- pinned: true # Always shown in featured section; never shuffled away (hero = most recent pinned)
159
- coverImage: "./images/cover.jpg" # Local or external URL
160
- latex: true # Enable KaTeX math
161
- toc: false # Hide table of contents
162
- layout: "simple" # Use simple layout (default: "post")
163
- externalLinks: # Links to external discussions
164
- - name: "Hacker News"
165
- url: "https://news.ycombinator.com/item?id=12345"
166
- - name: "V2EX"
167
- url: "https://v2ex.com/t/123456"
168
- redirectFrom: # Old URLs that should redirect to this post (prefix changes only)
169
- - /posts/my-old-slug
170
- - /old-series/my-old-slug
171
- ---
172
- ```
20
+ Content-creation scripts, test layout, validate pipeline → `docs/CONTRIBUTING.md`.
173
21
 
174
- ### Series (`content/series/[slug]/index.mdx`)
175
-
176
- ```yaml
177
- ---
178
- title: "Series Title"
179
- excerpt: "Series description"
180
- date: "2026-01-01"
181
- coverImage: "./images/cover.jpg"
182
- featured: true # Show in featured series
183
- draft: true # Hidden in production (default: false)
184
- sort: "date-asc" # 'date-asc' | 'date-desc' | 'manual'
185
- posts: ["post-1", "post-2"] # Manual post ordering (optional)
186
- ---
187
- ```
22
+ ## Architecture map
188
23
 
189
- ### Books (`content/books/[slug]/index.mdx`)
190
-
191
- ```yaml
192
- ---
193
- title: "Book Title"
194
- excerpt: "Book description"
195
- date: "2026-01-01"
196
- coverImage: "text:DG" # Cover image or text placeholder
197
- featured: true # Show in featured books
198
- draft: false
199
- authors: ["Author Name"]
200
- chapters:
201
- - part: "Part I: Getting Started" # Optional part grouping
202
- chapters:
203
- - title: "Chapter Title"
204
- id: "chapter-file" # Maps to chapter-file.mdx or chapter-file/index.mdx
205
- - part: "Part II: Advanced"
206
- chapters:
207
- - title: "Another Chapter"
208
- id: "another-chapter"
209
- ---
210
- ```
24
+ Quick "where do routes live" lookup. Full reference: `docs/ARCHITECTURE.md`.
25
+
26
+ - Standard routes follow folder names under `src/app/`: `/posts`, `/series`, `/tags`, `/notes`, `/books`, `/authors`, `/archive`, `/graph`, `/flows/[year]/[month]/[day]`. Book chapters are a catch-all: `books/[slug]/[...chapter]` (supports nested chapter IDs like `maths/linear/intro`).
27
+ - **Top-level `[slug]` and `[slug]/[postSlug]`** resolve `redirectFrom` aliases and `series.customPaths` — highest-risk dynamic surface; touch with care.
28
+ - Feeds at `feed.xml` / `feed.atom` / `all.xml` / `all.atom` / `flows/feed.{xml,atom}`; sitemap at `sitemap.ts`; search index at `search.json`.
29
+ - Rendering pipeline lives in `src/lib/`: Shiki (`shiki.ts`, `shiki-rst.ts`), remark/rehype plugins (`remark-github-alerts`, `remark-wikilinks`, `remark-code-group`, `rehype-fence-meta`, `rehype-image-metadata`), redirects (`series-redirects.ts`), feeds/JSON-LD (`feed-utils.ts`, `json-ld.ts`).
30
+
31
+ ## Design principles
32
+
33
+ - **Strict build over silent runtime failure.** Static export means misconfiguration must fail at build time. Use `throw` in `generateStaticParams` and similar — never silent skips or `console.warn`. Precedent: `validateSeriesAutoPaths` throws on slug collisions; `redirectFrom` alias conflicts (reserved slug or duplicate) should also throw, not produce broken redirects.
34
+ - **Exception**: fence-language resolution in `src/lib/shiki.ts` deliberately degrades to `plaintext` + a deduped `console.warn` instead of throwing. Authors can't reliably predict Shiki's alias coverage, and "typo" vs "legitimate community alias" are indistinguishable from our side, so production deploys shouldn't fail on a single unhighlighted code block. Real typos still surface via the warn output in local/CI build logs.
35
+ - **Validate author input at build time; keep content portable.** Frontmatter via Zod. Files on disk stay valid `.md` / `.mdx` / `.rst` — no Amytis-specific syntax that breaks other tools.
36
+
37
+ ## Integration-point rules (always go through X)
38
+
39
+ - **URLs:** always `getPostUrl()` / `getPostsBasePath()` / `getSeriesCustomPaths()` from `src/lib/urls.ts`. Never hardcode `/posts/[slug]` — posts may live at `/articles/[slug]`, custom prefixes (`series.customPaths`), or under `posts.basePath`.
40
+ - **Content reads:** always via `src/lib/markdown.ts` (`getAllPosts`, `getPostBySlug`, `getSeriesData`, etc.). Frontmatter validation belongs in its Zod schemas, not in route files.
41
+ - **Search utilities:** pure helpers (URL type detection, date extraction, title cleaning, markdown stripping) live in `src/lib/search-utils.ts` and are shared by the `Search` component and the search-index route. Don't duplicate them.
42
+ - **i18n strings:** add to `src/i18n/translations.ts`. Locale-aware config fields are `string | Record<string, string>`; resolve via `resolveLocale()` / `resolveLocaleValue()` from `src/lib/i18n.ts`.
43
+
44
+ ## Config sync
45
+
46
+ `site.config.ts` (this repo — i18n object form, `{ en, zh }`) and `site.config.example.ts` (shipped via `create-amytis` — plain strings, single-locale, optional features default disabled) must stay in sync. Any schema change to one must be mirrored in the other.
47
+
48
+ ## Gotchas (things Claude will get wrong on first try)
49
+
50
+ Feature-local gotchas live in path-scoped rules under `.claude/rules/` and auto-load when you touch matching files: `rst.md` (docutils setup, sanitize-html allowlist, disk-cache version) and `immersive-reading.md` (chrome-hiding hooks, provider boundaries, prefs persistence).
51
+
52
+ - **`turbopackIgnore` on fs reads.** Any `fs.readFileSync()` path expression must be preceded by `/* turbopackIgnore: true */` (see `src/lib/markdown.ts`, `src/lib/rehype-image-metadata.ts`). Missing it causes incorrect bundling.
53
+ - **No AVIF for `coverImage`.** Upstream bug in `next-image-export-optimizer` emits `.webp` files but a `srcset` pointing at `.avif` → 404 in prod. Use `.jpg` / `.png` / `.webp`. See `docs/TROUBLESHOOTING.md`.
54
+ - **Unicode slugs.** Dynamic route pages call `safeDecodeParam()` and try decoded / raw / NFC / NFD variants — don't shortcut with bare `decodeURIComponent()` (it throws on malformed input). When touching dynamic routes, verify both ASCII and Unicode slugs.
55
+ - **`generateStaticParams` returns raw values.** Don't `encodeURIComponent` route params; Next.js handles encoding. Don't link to placeholder routes like `/posts/[slug]` — always link to concrete URLs.
56
+ - **Series format is locked.** A series index can be `index.md` / `.mdx` / `README.md` / `README.mdx` / `index.rst` / `README.rst` (first match wins). All child posts must match. Mixing formats is a build error.
57
+ - **Pagefind index.** `bun run build:dev` regenerates `public/pagefind/`; search returns stale results until you rerun it after content changes.
58
+ - **`trailingSlash: true` is load-bearing.** Lets co-located post assets (`posts/slug/images/`) coexist with `posts/slug/index.html`. Don't flip it in `next.config.ts`.
59
+ - **Shiki highlighter is a `globalThis` singleton.** Never instantiate it per render — `createHighlighter` loads Oniguruma WASM + grammars (~1–2 s). See `src/lib/shiki.ts`.
60
+ - **Fence meta needs `rehype-fence-meta` BEFORE `rehype-raw`.** `mdast-util-to-hast` stores fence meta on `node.data.meta`, which `rehype-raw` drops during HTML round-trip. The plugin copies it to a real `data-meta` attribute first. Order matters in `MarkdownRenderer.tsx`.
61
+ - **GitHub alerts (`> [!NOTE]`, etc.) need the custom `remarkGithubAlerts` plugin.** `remark-gfm` v4 does NOT transform `[!TYPE]` blockquotes — they pass through as plain blockquotes with the literal marker visible. The custom plugin in `src/lib/remark-github-alerts.ts` is what detects them and routes to `<GithubAlert>`. If a future remark-gfm adds native alert support, that's a regression to watch for (covered by an integration test).
62
+ - **Single-line block math `$$ x $$` is silently inline.** `micromark-extension-math` (under `remark-math` v6) requires the `$$` markers on their own lines — single-line collapses to *inline* math (no `katex-display` wrapper, no centering, no display margin) and looks like a subtly under-styled formula. `src/lib/normalize-vuepress-math.ts` expands single-line `$$ x $$` to opener / body / closer before parsing, so authored content stays portable. If a chapter formula stops centering, suspect the normalizer's regex first.
63
+
64
+ ## Development workflow
65
+
66
+ For a new feature or non-trivial change:
67
+
68
+ - **Branch.** Work on a dedicated `<type>/<topic>` branch off `main`, not directly on `main`.
69
+ - **Commit in focused slices.** One commit per logical slice (e.g. split a dead-code removal from the feature that replaces it). Keep `bun run lint` green at each commit so the branch stays bisectable. Conventional Commits: `feat | fix | refactor | perf | chore | docs | test | release`; subject under ~70 chars; body explains *why*; no `Co-Authored-By` trailers.
70
+ - **Tests + docs in the same change.** Update `docs/ARCHITECTURE.md` / `docs/CONTRIBUTING.md` / `docs/TROUBLESHOOTING.md` alongside seam/workflow/invariant changes — not as a follow-up.
71
+ - **Open a PR** into `main` when the branch is green. Do not add the `🤖 Generated with [Claude Code]` footer to PR descriptions. Pushing, and opening PRs are actions the user authorizes — don't push or open a PR unless asked.
72
+
73
+ ## Verifying a change
74
+
75
+ - **Default verify loop: `bun run lint && bun run test` only.** Stop here.
76
+ - **Do NOT run `bun run build:dev` (or `bun run build` / `bun run validate`) unless the user asks OR it is genuinely needed.** It takes ~1–2 minutes (Turbopack compile + 800+ static pages + Pagefind index) and the cost adds up fast across many small steps. None of these are "needed" on their own: markdown pipeline edits, schema changes, route moves, branch tip, pre-PR readiness, "just to be safe."
77
+ - "Genuinely needed" means lint+test **cannot** catch the failure mode you're worried about — e.g. a strict-build invariant that only fires during static export (a `throw` in `generateStaticParams`, a `redirectFrom` collision, a Zod-schema failure that only triggers on real content). If unsure, prefer asking over running.
78
+ - `bun run validate` chains lint + test + build:dev; same rule — same bar.
79
+ - Touched `src/lib/markdown.ts`, `src/lib/urls.ts`, or `site.config.ts`? Add an integration test under `tests/integration/`.
80
+ - Touched any dynamic route? Verify both ASCII and Unicode slugs render.
81
+
82
+ ## Context compression hints
83
+
84
+ When compressing history, preserve in priority order:
85
+
86
+ 1. **Build-time invariants touched** — strict-build/throw boundaries, Zod schemas, `site.config.ts` shape, helpers in `src/lib/urls.ts` / `src/lib/markdown.ts`.
87
+ 2. **Modified files and why** — file list with one-line reasons, not the diff.
88
+ 3. **Verification status** — `bun run lint && bun test` (and `build:dev` if run) pass/fail.
89
+ 4. **Cache version bumps** — `RST_RENDERER_DISK_CACHE_VERSION` etc.; easy to lose after compression.
90
+ 5. **Open TODOs and rollback notes.**
91
+ 6. **Tool output**: drop; keep pass/fail summary only.
92
+
93
+ ## Where to find more
211
94
 
212
- ## Key Components
213
-
214
- - `PostLayout` / `SimpleLayout` - Post page layouts with TOC, series sidebar, external links, comments
215
- - `Hero` - Configurable homepage hero section with collapsible intro
216
- - `HorizontalScroll` - Scrollable container with navigation arrows for featured content
217
- - `PostList` - Card-based post listing with thumbnails, metadata, excerpts, and tags
218
- - `SeriesCatalog` - Timeline-style series post listing with numbered entries and progress indicator
219
- - `SeriesSidebar` - Series navigation sidebar with progress bar and color-coded states
220
- - `SeriesList` - Mobile-optimized series navigation matching sidebar design
221
- - `Search` - Full-text search (Cmd/Ctrl+K) powered by Pagefind (build-time index); features type filter tabs (All/Post/Flow/Book), recent searches, keyboard navigation (arrows + number keys), debounced input, body scroll lock, focus trap, ARIA accessibility, and search syntax tips (`"phrase"`, `-exclude`)
222
- - `TableOfContents` - Sticky TOC with scroll tracking, reading progress, and back-to-top
223
- - `MarkdownRenderer` - MDX rendering with GFM, math, syntax highlighting, diagrams
224
- - `CoverImage` - Optimized image component with WebP support
225
- - `Comments` - Giscus or Disqus integration (theme-aware)
226
- - `Analytics` - Umami, Plausible, or Google Analytics integration
227
- - `FlowContent` - Client wrapper for flow pages with tag filtering state management
228
- - `FlowCalendarSidebar` - Calendar sidebar with date navigation, browse panel, and clickable tag filters
229
- - `FlowTimelineEntry` - Individual flow entry in timeline list
230
- - `LanguageSwitch` - i18n language selector
231
- - `ThemeToggle` - Light/dark mode toggle
95
+ - `docs/ARCHITECTURE.md` — route map, content model, components, data layer, frontmatter schemas, full configuration reference
96
+ - `docs/CONTRIBUTING.md` — full command list, test layout, content-creation scripts
97
+ - `docs/TROUBLESHOOTING.md` known issues (AVIF, dev-mode browser-extension CSP/SharedStorage noise)
98
+ - `docs/ALERTS.md` / `docs/CODE-BLOCKS.md` / `docs/DIGITAL_GARDEN.md` content-feature references (alert callouts, code-block features, garden philosophy)
99
+ - `docs/deployment.md` production deploy steps
100
+ - `docs/guides/` task-oriented walkthroughs (e.g. `importing-vuepress-books.md`)
101
+ - `site.config.ts` live config (read it directly; don't infer from this file)