@hutusi/amytis 1.11.0 → 1.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.
Files changed (72) hide show
  1. package/AGENTS.md +7 -4
  2. package/CHANGELOG.md +23 -0
  3. package/CLAUDE.md +11 -0
  4. package/README.md +53 -5
  5. package/README.zh.md +36 -6
  6. package/bun.lock +9 -0
  7. package/content/posts/2026-01-12-the-art-of-algorithms.mdx +3 -0
  8. package/content/posts/2026-01-21-kitchen-sink/index.mdx +2 -2
  9. package/content/posts/multimedia-showcase/index.mdx +4 -6
  10. package/content/series/markdown-showcase/syntax-highlighting.mdx +1 -1
  11. package/content/series/modern-web-dev/index.mdx +13 -0
  12. package/content/series/nextjs-deep-dive/01-getting-started.mdx +1 -1
  13. package/docs/ARCHITECTURE.md +3 -0
  14. package/docs/CONTRIBUTING.md +29 -0
  15. package/docs/TROUBLESHOOTING.md +31 -0
  16. package/next.config.ts +8 -2
  17. package/nginx.conf.example +156 -0
  18. package/package.json +6 -2
  19. package/packages/create-amytis/src/index.test.ts +2 -2
  20. package/playwright.config.ts +199 -0
  21. package/public/images/vibrant-waves.jpg +0 -0
  22. package/scripts/add-series-redirects.ts +192 -0
  23. package/scripts/import-obsidian.ts +319 -0
  24. package/site.config.example.ts +17 -2
  25. package/site.config.ts +17 -2
  26. package/src/app/[slug]/[postSlug]/page.tsx +64 -10
  27. package/src/app/[slug]/page.tsx +48 -10
  28. package/src/app/flows/[year]/[month]/[day]/page.tsx +7 -1
  29. package/src/app/layout.tsx +1 -2
  30. package/src/app/notes/[slug]/page.tsx +31 -7
  31. package/src/app/page.tsx +6 -6
  32. package/src/app/posts/[slug]/page.tsx +36 -4
  33. package/src/app/series/[slug]/page/[page]/page.tsx +6 -5
  34. package/src/app/series/[slug]/page.tsx +8 -7
  35. package/src/app/tags/[tag]/page.tsx +6 -1
  36. package/src/components/CoverImage.tsx +3 -1
  37. package/src/components/CuratedSeriesSection.tsx +5 -4
  38. package/src/components/FeaturedStoriesSection.tsx +12 -11
  39. package/src/components/Footer.tsx +1 -1
  40. package/src/components/Hero.tsx +2 -2
  41. package/src/components/KatexStyles.tsx +8 -0
  42. package/src/components/LatestWritingSection.tsx +5 -9
  43. package/src/components/MarkdownRenderer.test.tsx +3 -0
  44. package/src/components/MarkdownRenderer.tsx +7 -2
  45. package/src/components/Navbar.tsx +11 -10
  46. package/src/components/PostList.tsx +7 -7
  47. package/src/components/PostNavigation.tsx +39 -14
  48. package/src/components/PostSidebar.tsx +39 -19
  49. package/src/components/RecentNotesSection.tsx +3 -6
  50. package/src/components/RedirectPage.tsx +27 -0
  51. package/src/components/SelectedBooksSection.tsx +4 -4
  52. package/src/components/SeriesCatalog.tsx +7 -4
  53. package/src/components/SeriesList.tsx +38 -22
  54. package/src/components/TagSidebar.tsx +3 -3
  55. package/src/components/TagsIndexClient.tsx +1 -1
  56. package/src/i18n/translations.ts +4 -0
  57. package/src/layouts/BookLayout.tsx +11 -0
  58. package/src/layouts/PostLayout.tsx +36 -18
  59. package/src/layouts/SimpleLayout.tsx +13 -1
  60. package/src/lib/comments.test.ts +50 -0
  61. package/src/lib/comments.ts +25 -0
  62. package/src/lib/markdown.ts +132 -23
  63. package/src/lib/remark-wikilinks.test.ts +86 -0
  64. package/src/lib/remark-wikilinks.ts +1 -1
  65. package/src/lib/urls.ts +52 -1
  66. package/tests/e2e/mobile/mobile-compat.spec.ts +407 -0
  67. package/tests/integration/collections.test.ts +129 -0
  68. package/tests/tooling/add-series-redirects.test.ts +110 -0
  69. package/tests/tooling/import-obsidian.test.ts +96 -0
  70. package/tests/unit/static-params.test.ts +99 -4
  71. package/tests/unit/urls.test.ts +30 -0
  72. package/public/images/vibrant-waves.avif +0 -0
package/AGENTS.md CHANGED
@@ -6,8 +6,10 @@
6
6
  - `src/lib/`: Content parsing and shared logic (`markdown.ts`, feed helpers, URL helpers, rehype/remark utilities).
7
7
  - `content/`: Markdown/MDX source content for posts, series, books, notes, flows, and static pages. Use folder posts (`index.mdx` + `images/` or `assets/`) when media is co-located.
8
8
  - `tests/`: Integration/e2e/tooling suites; keep focused utility tests near source (example: `src/lib/markdown.test.ts`).
9
- - `scripts/`: Bun-based authoring/import tooling.
9
+ - `scripts/`: Bun-based authoring/import/build tooling.
10
+ - `imports/`: Local-only staging area for chat logs, PDFs, and image folders consumed by import scripts; see `imports/README.md`.
10
11
  - `packages/create-amytis/`: The `bun create amytis` scaffold CLI; keep its tests aligned with repo scaffolding behavior.
12
+ - `site.config.ts` is the live site configuration; `site.config.example.ts` is the fuller reference template.
11
13
 
12
14
  ## Build, Test, and Development Commands
13
15
  - `bun install`: Install dependencies.
@@ -18,8 +20,8 @@
18
20
  - `bun run validate`: Run the recommended pre-PR validation sequence (`lint`, `test`, `build:dev`).
19
21
  - `bun run clean`: Remove generated outputs (`.next`, `out`, `public/posts`, `public/books`, `public/flows`).
20
22
  - `bun run lint`: Run ESLint.
21
- - `bun test` / `bun run test:unit` / `bun run test:int` / `bun run test:e2e`: Run all or scoped tests.
22
- - Content tooling: `bun run new`, `bun run new-weekly`, `bun run new-series`, `bun run new-note`, `bun run new-flow`, `bun run new-flow-from-chat`, `bun run new-from-pdf`, `bun run new-from-images`, `bun run import-book`, `bun run sync-book`, `bun run series-draft`.
23
+ - `bun test` / `bun run test:unit` / `bun run test:int` / `bun run test:e2e` / `bun run test:mobile`: Run all or scoped tests.
24
+ - Content tooling: `bun run new`, `bun run new-weekly`, `bun run new-series`, `bun run new-note`, `bun run new-flow`, `bun run new-flow-from-chat`, `bun run new-from-pdf`, `bun run new-from-images`, `bun run import-obsidian`, `bun run import-book`, `bun run sync-book`, `bun run series-draft`, `bun run add-series-redirects`.
23
25
 
24
26
  ## Coding Style & Naming Conventions
25
27
  - Use TypeScript for app and utility code; MDX/Markdown for content.
@@ -31,7 +33,8 @@
31
33
  - Project uses `output: "export"` and `trailingSlash: true` in `next.config.ts`.
32
34
  - In `generateStaticParams()`, return raw segment values; do not pre-encode with `encodeURIComponent`.
33
35
  - Never link to route placeholders like `/posts/[slug]`; always link to concrete slugs (for example, `/posts/中文测试文章`).
34
- - Posts may also resolve through custom top-level paths via `series.customPaths` and `[slug]/[postSlug]`; preserve those URL helpers instead of hardcoding paths.
36
+ - Posts default to `/<posts.basePath>/<slug>`, but may also resolve through `series.autoPaths`, `series.customPaths`, and `[slug]/[postSlug]`; preserve URL helpers instead of hardcoding paths.
37
+ - Before moving series posts away from the default posts path, run `bun run add-series-redirects --dry-run` and then `bun run add-series-redirects` so legacy URLs keep resolving.
35
38
  - When touching dynamic routes, verify both ASCII and Unicode paths.
36
39
 
37
40
  ## Testing Guidelines
package/CHANGELOG.md CHANGED
@@ -5,6 +5,29 @@ 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.12.0] - 2026-03-08
9
+
10
+ ### Added
11
+ - **Series Auto Paths**: Added `series.autoPaths` so series posts can be served directly at `/<series-slug>/<post-slug>` without manual per-series path mapping.
12
+ - **URL Migration Tooling**: Added `bun run add-series-redirects` with `--dry-run` and `--auto-paths` support to backfill `redirectFrom` entries before route migrations.
13
+ - **Collections**: Added curated cross-series collections with dedicated data-layer support, listing surfaces, and integration test coverage.
14
+ - **Static Page Comments**: Added per-category comment controls plus optional comments on book chapters, notes, and flows.
15
+ - **Obsidian Importer**: Added `bun run import-obsidian` for recursive vault import with wikilink rewriting and relative-path handling.
16
+ - **Mobile Coverage**: Added Playwright-based mobile compatibility testing and homepage/navigation assertions across real-device profiles.
17
+
18
+ ### Changed
19
+ - **Wikilink Readability**: Wikilinks now prefer rendered note titles over raw slugs where metadata is available.
20
+ - **Homepage Behavior**: Clicking the logo on the homepage now scrolls back to the top, and section spacing stays consistent when optional homepage blocks are disabled.
21
+ - **Documentation**: Refreshed repository guidance, architecture notes, and release-facing docs to match current commands, routing rules, and import workflows.
22
+
23
+ ### Fixed
24
+ - **Redirect Safety**: Prevented `redirectFrom` aliases from hijacking reserved routes or existing pages, including conflicting and single-segment alias cases.
25
+ - **Custom Route Redirects**: Fixed renamed-slug redirects for `series.customPaths` and `[slug]/[postSlug]` routes.
26
+ - **Collection Integrity**: Fixed duplicate collection entries, draft leakage in production, and prev/next navigation inconsistencies.
27
+ - **Tag URL Consistency**: Normalized mixed-case tag URLs without losing display casing and removed duplicate counting from same-document tag variants.
28
+ - **Unicode Note Routing**: Fixed dev-mode handling for percent-encoded Unicode note slugs.
29
+ - **Mobile UX**: Improved narrow-viewport layout stability, tap-target sizing, and flaky mobile test behavior.
30
+
8
31
  ## [1.11.0] - 2026-03-07
9
32
 
10
33
  ### Changed
package/CLAUDE.md CHANGED
@@ -18,6 +18,7 @@ bun test # Run all tests
18
18
  bun run test:unit # Run unit tests (src/)
19
19
  bun run test:int # Run integration tests
20
20
  bun run test:e2e # Run end-to-end tests
21
+ bun run test:mobile # Run Playwright mobile compatibility tests (requires dev server)
21
22
  bun test path/to/file.test.ts # Run a single test file
22
23
 
23
24
  # Build
@@ -34,11 +35,18 @@ bun run new-series "Series Name" # Create new series
34
35
  bun run new-from-pdf doc.pdf # Create post from PDF
35
36
  bun run new-from-images ./photos # Create post from image folder
36
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
37
41
  bun run new-flow-from-chat # Import all new files from imports/chats/
38
42
  bun run sync-book # Sync chapters list for all books from disk
39
43
  bun run sync-book <slug> # Sync chapters list for one book
40
44
  ```
41
45
 
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
+
42
50
  ## Architecture
43
51
 
44
52
  ### Data Flow
@@ -157,6 +165,9 @@ externalLinks: # Links to external discussions
157
165
  url: "https://news.ycombinator.com/item?id=12345"
158
166
  - name: "V2EX"
159
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
160
171
  ---
161
172
  ```
162
173
 
package/README.md CHANGED
@@ -119,6 +119,7 @@ The scaffold command downloads the latest tagged Amytis release, installs depend
119
119
  ## Core
120
120
  bun dev
121
121
  bun run lint
122
+ bun run build:graph
122
123
  bun run validate
123
124
 
124
125
  ## Build & Deploy
@@ -132,6 +133,7 @@ bun test
132
133
  bun run test:unit
133
134
  bun run test:int
134
135
  bun run test:e2e
136
+ bun run test:mobile
135
137
 
136
138
  ## Create Content
137
139
  bun run new "Post Title"
@@ -144,9 +146,11 @@ bun run new-flow
144
146
  bun run new-from-pdf ./doc.pdf
145
147
  bun run new-from-images ./photos
146
148
  bun run new-flow-from-chat
149
+ bun run import-obsidian
147
150
  bun run import-book
148
151
  bun run sync-book
149
152
  bun run series-draft "series-slug"
153
+ bun run add-series-redirects --dry-run
150
154
  ```
151
155
 
152
156
  ### Importing Chat Logs to Flows
@@ -162,7 +166,7 @@ Import history is stored in `imports/chats/.imported`.
162
166
 
163
167
  ## Configuration
164
168
 
165
- All site settings are managed in `site.config.ts`:
169
+ Primary site settings live in `site.config.ts`. `site.config.example.ts` is the reference template used by the scaffold and is useful when reviewing new options:
166
170
 
167
171
  ```typescript
168
172
  export const siteConfig = {
@@ -182,27 +186,66 @@ export const siteConfig = {
182
186
  };
183
187
  ```
184
188
 
189
+ High-impact areas to customize first:
190
+
191
+ - Site identity: `title`, `description`, `baseUrl`, `ogImage`, `logo`
192
+ - Navigation and footer: `nav`, `footer`, `subscribe`, `social`
193
+ - Content behavior: `posts.basePath`, `posts.includeDateInUrl`, `series.autoPaths`, `series.customPaths`
194
+ - Homepage composition: `hero`, `homepage.sections`
195
+ - Integrations: `analytics`, `comments`, `feed`, `i18n`
196
+
197
+ For static hosting behind nginx, start from `nginx.conf.example`.
198
+
199
+ ## Static Export Routing Rules
200
+
201
+ Amytis is built around Next.js static export with `output: "export"` and `trailingSlash: true`.
202
+
203
+ - In `generateStaticParams()`, return raw segment values. Do not pre-encode with `encodeURIComponent`.
204
+ - Link to concrete URLs such as `/posts/中文测试文章`, not route placeholders like `/posts/[slug]`.
205
+ - Posts default to `/<posts.basePath>/<slug>` and `posts.basePath` defaults to `/posts`.
206
+ - If `series.autoPaths` is enabled, series posts move to `/<series-slug>/<post-slug>`.
207
+ - If `series.customPaths` is configured, those custom prefixes override `autoPaths`.
208
+ - Before moving series posts off the default posts path, run `bun run add-series-redirects --dry-run` and then `bun run add-series-redirects` so legacy URLs still resolve.
209
+
185
210
  ## Writing Content
186
211
 
187
212
  ### Posts
188
213
 
189
214
  Create `.md` or `.mdx` files in `content/posts/`.
190
215
 
216
+ - Flat file: `content/posts/my-post.mdx`
217
+ - Date-prefixed file: `content/posts/2026-01-01-my-post.mdx`
218
+ - Folder post with co-located media: `content/posts/my-post/index.mdx` plus `content/posts/my-post/images/*`
219
+ - CLI: `bun run new "Post Title"` or `bun run new "Post Title" --folder`
220
+
191
221
  ### Flows
192
222
 
193
- Create daily notes in `content/flows/YYYY/MM/DD.mdx` or `content/flows/YYYY/MM/DD/index.mdx`.
223
+ Create daily notes in `content/flows/YYYY/MM/DD.md` or `.mdx`.
224
+
225
+ - CLI: `bun run new-flow` creates today’s entry
226
+ - Chat import: put exports in `imports/chats/` and run `bun run new-flow-from-chat`
194
227
 
195
228
  ### Series
196
229
 
197
- Create a directory in `content/series/` with an `index.mdx`.
230
+ Create a directory in `content/series/<slug>/` with an `index.mdx`, then add posts as sibling files or folders.
231
+
232
+ - CLI: `bun run new-series "Series Name"`
233
+ - You can also create a post directly inside an existing series with `bun run new "Post Title" --series <series-slug>`
198
234
 
199
235
  ### Books
200
236
 
201
- Books are for long-form, structured content. Create a directory in `content/books/`.
237
+ Books are long-form structured content under `content/books/<slug>/`.
238
+
239
+ - Keep book metadata in `index.mdx`
240
+ - Add chapter files beside it, for example `introduction.mdx` or `setup.mdx`
241
+ - Use `bun run import-book` and `bun run sync-book` for book-oriented workflows
202
242
 
203
243
  ### Notes
204
244
 
205
- Create evergreen notes in `content/notes/` (e.g., `concept.mdx`). Use `[[wiki-links]]` to connect them.
245
+ Create evergreen notes in `content/notes/` (for example `concept.mdx`). Use `[[wiki-links]]` to connect them.
246
+
247
+ - CLI: `bun run new-note "Concept"`
248
+ - Unicode slugs are supported intentionally for notes and posts where needed
206
249
 
207
250
  ## Project Structure
208
251
 
@@ -215,7 +258,10 @@ amytis/
215
258
  notes/ # Digital garden notes
216
259
  flows/ # Daily notes (YYYY/MM/DD)
217
260
  about.mdx # Static pages
261
+ docs/ # Architecture, deployment, troubleshooting
262
+ imports/ # Local-only input files for import scripts
218
263
  public/ # Static assets
264
+ scripts/ # Bun authoring/build/import tooling
219
265
  src/
220
266
  app/ # Next.js App Router pages
221
267
  books/ # Book routes
@@ -225,6 +271,7 @@ amytis/
225
271
  components/ # React components
226
272
  lib/
227
273
  markdown.ts # Data access layer
274
+ tests/ # Unit, integration, e2e, tooling tests
228
275
  packages/
229
276
  create-amytis/ # `bun create amytis` scaffold CLI
230
277
  site.config.ts # Site configuration
@@ -236,6 +283,7 @@ amytis/
236
283
  - [Deployment Guide](docs/deployment.md)
237
284
  - [Digital Garden Guide](docs/DIGITAL_GARDEN.md)
238
285
  - [Contributing Guide](docs/CONTRIBUTING.md)
286
+ - [Troubleshooting](docs/TROUBLESHOOTING.md)
239
287
 
240
288
  ## License
241
289
 
package/README.zh.md CHANGED
@@ -110,6 +110,7 @@ bun dev
110
110
  ## Core
111
111
  bun dev
112
112
  bun run lint
113
+ bun run build:graph
113
114
  bun run validate
114
115
 
115
116
  ## Build & Deploy
@@ -123,6 +124,7 @@ bun test
123
124
  bun run test:unit
124
125
  bun run test:int
125
126
  bun run test:e2e
127
+ bun run test:mobile
126
128
 
127
129
  ## Create Content
128
130
  bun run new "Post Title"
@@ -135,9 +137,11 @@ bun run new-flow
135
137
  bun run new-from-pdf ./doc.pdf
136
138
  bun run new-from-images ./photos
137
139
  bun run new-flow-from-chat
140
+ bun run import-obsidian
138
141
  bun run import-book
139
142
  bun run sync-book
140
143
  bun run series-draft "series-slug"
144
+ bun run add-series-redirects --dry-run
141
145
  ```
142
146
 
143
147
  ### 导入聊天记录到 Flows
@@ -153,15 +157,37 @@ bun run new-flow-from-chat
153
157
 
154
158
  ## 配置
155
159
 
156
- 所有站点配置集中在 `site.config.ts`。
160
+ 主要站点配置集中在 `site.config.ts`,`site.config.example.ts` 则是更完整的参考模板,适合查看可选项和默认写法。
161
+
162
+ 优先关注这些配置区块:
163
+
164
+ - 站点信息:`title`、`description`、`baseUrl`、`ogImage`、`logo`
165
+ - 导航与页脚:`nav`、`footer`、`subscribe`、`social`
166
+ - 内容路由:`posts.basePath`、`posts.includeDateInUrl`、`series.autoPaths`、`series.customPaths`
167
+ - 首页结构:`hero`、`homepage.sections`
168
+ - 集成能力:`analytics`、`comments`、`feed`、`i18n`
169
+
170
+ 如果你部署到 nginx,可直接从 `nginx.conf.example` 开始调整。
171
+
172
+ ## 静态导出路由规则
173
+
174
+ Amytis 基于 Next.js 静态导出,核心约束是 `output: "export"` 和 `trailingSlash: true`。
175
+
176
+ - 在 `generateStaticParams()` 中返回原始路径片段,不要手动用 `encodeURIComponent`
177
+ - 链接必须指向真实路径,例如 `/posts/中文测试文章`,不要写 `/posts/[slug]`
178
+ - 文章默认路径是 `/<posts.basePath>/<slug>`,其中 `posts.basePath` 默认值为 `/posts`
179
+ - 启用 `series.autoPaths` 后,系列文章会切换到 `/<series-slug>/<post-slug>`
180
+ - 配置了 `series.customPaths` 时,会优先使用自定义前缀覆盖 `autoPaths`
181
+ - 如果准备把系列文章从默认 `/posts/...` 路径迁走,先执行 `bun run add-series-redirects --dry-run`,确认后再执行 `bun run add-series-redirects`
157
182
 
158
183
  ## 内容写作
159
184
 
160
- - **Posts**:创建到 `content/posts/`
161
- - **Flows**:创建到 `content/flows/YYYY/MM/DD.mdx`(或文件夹模式)
162
- - **Series**:创建 `content/series/<slug>/index.mdx`
163
- - **Books**:创建到 `content/books/<slug>/`
164
- - **Notes**:创建到 `content/notes/`,支持 `[[wiki-links]]`
185
+ - **Posts**:写入 `content/posts/`,支持单文件、日期前缀和文件夹模式。文件夹模式可将图片放在同目录的 `images/` 中。CLI:`bun run new "Post Title"` 或 `bun run new "Post Title" --folder`
186
+ - **Flows**:写入 `content/flows/YYYY/MM/DD.md` 或 `.mdx`。CLI:`bun run new-flow`
187
+ - **Series**:创建 `content/series/<slug>/index.mdx`,系列内文章可作为同级文件或子目录。CLI:`bun run new-series "Series Name"`
188
+ - **Books**:写入 `content/books/<slug>/`,通常用 `index.mdx` 存元数据,其余章节文件与其并列
189
+ - **Notes**:写入 `content/notes/`,支持 `[[wiki-links]]`。CLI:`bun run new-note "Concept"`
190
+ - **Unicode slug**:文章和笔记支持有意使用 Unicode slug,修改动态路由时要一起验证 ASCII 和 Unicode 路径
165
191
 
166
192
  ## 项目结构
167
193
 
@@ -173,11 +199,15 @@ amytis/
173
199
  books/
174
200
  notes/
175
201
  flows/
202
+ docs/
203
+ imports/
176
204
  public/
205
+ scripts/
177
206
  src/
178
207
  app/
179
208
  components/
180
209
  lib/
210
+ tests/
181
211
  packages/
182
212
  create-amytis/
183
213
  site.config.ts
package/bun.lock CHANGED
@@ -34,6 +34,7 @@
34
34
  "zod": "^4.3.6",
35
35
  },
36
36
  "devDependencies": {
37
+ "@playwright/test": "^1.58.2",
37
38
  "@tailwindcss/postcss": "^4.1.18",
38
39
  "@types/bun": "^1.3.9",
39
40
  "@types/d3": "^7.4.3",
@@ -286,6 +287,8 @@
286
287
 
287
288
  "@pagefind/windows-x64": ["@pagefind/windows-x64@1.4.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g=="],
288
289
 
290
+ "@playwright/test": ["@playwright/test@1.58.2", "", { "dependencies": { "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" } }, "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA=="],
291
+
289
292
  "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
290
293
 
291
294
  "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
@@ -792,6 +795,8 @@
792
795
 
793
796
  "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="],
794
797
 
798
+ "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
799
+
795
800
  "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
796
801
 
797
802
  "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="],
@@ -1232,6 +1237,10 @@
1232
1237
 
1233
1238
  "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
1234
1239
 
1240
+ "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="],
1241
+
1242
+ "playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="],
1243
+
1235
1244
  "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="],
1236
1245
 
1237
1246
  "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="],
@@ -5,6 +5,9 @@ excerpt: "Implementing and visualizing the QuickSort algorithm."
5
5
  category: "Computer Science"
6
6
  tags: ["algorithms", "sorting", "typescript"]
7
7
  authors: ["Algo Master", "Math Wizard"]
8
+ redirectFrom:
9
+ - /this-is-a-test-redirect-for-the-art-of-algorithms
10
+ - /this/is-a-test-redirect-for-the-art-of-algorithms
8
11
  ---
9
12
 
10
13
  Sorting is a fundamental concept in computer science. Let's look at **QuickSort**.
@@ -142,9 +142,9 @@ sequenceDiagram
142
142
 
143
143
  ![Galaxy](/images/galaxy.jpg)
144
144
 
145
- ### Vibrant Waves (AVIF format)
145
+ ### Vibrant Waves (JPG format)
146
146
 
147
- ![Vibrant Waves](/images/vibrant-waves.avif)
147
+ ![Vibrant Waves](/images/vibrant-waves.jpg)
148
148
 
149
149
  ### Side by Side (Raw HTML)
150
150
 
@@ -20,8 +20,7 @@ Responsive 16:9 YouTube embed using a padded wrapper:
20
20
  <iframe
21
21
  src="https://www.youtube.com/embed/jNQXAC9IVRw?rel=0"
22
22
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
23
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
24
- allowfullscreen
23
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture; web-share"
25
24
  title="Me at the zoo — first YouTube video ever uploaded (2005)"
26
25
  ></iframe>
27
26
  </div>
@@ -39,7 +38,6 @@ Vimeo also supports responsive 16:9 embedding:
39
38
  src="https://player.vimeo.com/video/76979871?color=ff9933&title=0&byline=0&portrait=0"
40
39
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
41
40
  allow="autoplay; fullscreen; picture-in-picture"
42
- allowfullscreen
43
41
  title="Big Buck Bunny — Blender Foundation (Vimeo)"
44
42
  ></iframe>
45
43
  </div>
@@ -114,8 +112,7 @@ Below is NASA's public live stream:
114
112
  <iframe
115
113
  src="https://www.youtube.com/embed/xAieE-QtOeM?autoplay=0"
116
114
  style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;"
117
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
118
- allowfullscreen
115
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture"
119
116
  title="NASA TV Live"
120
117
  ></iframe>
121
118
  </div>
@@ -130,7 +127,7 @@ Twitch requires your site's hostname in the `parent` query parameter (so the emb
130
127
  src="https://player.twitch.tv/?channel=CHANNEL_NAME&parent=your-domain.com"
131
128
  height="378"
132
129
  width="100%"
133
- allowfullscreen
130
+ allow="fullscreen"
134
131
  title="Twitch Stream"
135
132
  ></iframe>
136
133
  ```
@@ -249,6 +246,7 @@ When embedding third-party media, keep these in mind:
249
246
  - **`loading="lazy"`**: Add this to below-the-fold embeds to defer loading and improve page speed.
250
247
  - **`title` attribute**: Always provide a descriptive `title` for screen reader accessibility.
251
248
  - **Privacy-enhanced mode**: YouTube supports `youtube-nocookie.com` to reduce tracking when embeds don't auto-play.
249
+ - **`allow` over `allowfullscreen`**: Use `allow="fullscreen"` instead of the deprecated `allowfullscreen` boolean attribute. Note that bare `allow="fullscreen"` restricts fullscreen to the iframe's own origin (more secure), whereas the legacy `allowfullscreen` was equivalent to `allow="fullscreen *"` (any origin). For third-party embeds like YouTube or Vimeo, `allow="fullscreen"` is the correct and preferred form. When both attributes are present the browser warns that `allow` takes precedence.
252
250
  - **`sandbox` attribute**: Use for untrusted embeds to restrict what the iframe can do.
253
251
  - **Twitch `parent` parameter**: Twitch requires your site's hostname in `parent` for embeds to work outside localhost.
254
252
 
@@ -4,7 +4,7 @@ date: "2026-02-15"
4
4
  category: "Engineering"
5
5
  tags: ["code", "typescript"]
6
6
  featured: true
7
- coverImage: "/images/vibrant-waves.avif"
7
+ coverImage: "/images/vibrant-waves.jpg"
8
8
  ---
9
9
 
10
10
  Amytis provides beautiful syntax highlighting for dozens of programming languages. Here are a few examples within the series context.
@@ -0,0 +1,13 @@
1
+ ---
2
+ type: collection
3
+ title: "Modern Web Development"
4
+ excerpt: "A curated path through modern web development: JavaScript fundamentals, React patterns, and deep Next.js mastery."
5
+ date: "2026-03-01"
6
+ featured: true
7
+ items:
8
+ - post: asynchronous-javascript
9
+ - post: understanding-react-hooks
10
+ - series: nextjs-deep-dive
11
+ ---
12
+
13
+ This collection assembles the essential reading for anyone building modern web applications. Start with the JavaScript fundamentals that underpin everything, move into React patterns, then go deep on Next.js.
@@ -3,7 +3,7 @@ title: "Part 1: Getting Started with Next.js 15"
3
3
  date: "2026-01-30"
4
4
  excerpt: "Setting up the environment and understanding the core philosophy."
5
5
  featured: true
6
- coverImage: "/images/vibrant-waves.avif"
6
+ coverImage: "/images/vibrant-waves.jpg"
7
7
  ---
8
8
 
9
9
  # Getting Started
@@ -70,8 +70,11 @@ src/app/
70
70
  - `next.config.ts` sets `output: "export"` and `trailingSlash: true`.
71
71
  - Post URLs use `getPostUrl()` in `src/lib/urls.ts`:
72
72
  - Default: `/<posts.basePath>/<post.slug>` (basePath defaults to `posts`)
73
+ - Series auto path: `/<series.slug>/<post.slug>` when `series.autoPaths` is enabled
73
74
  - Series override: `/<series.customPaths[seriesSlug]>/<post.slug>`
74
75
  - Dynamic route params should return raw segment values from `generateStaticParams()` (do not pre-encode values).
76
+ - Links should always target concrete paths, not route placeholders such as `/posts/[slug]`.
77
+ - When moving series posts off the default posts path, `scripts/add-series-redirects.ts` updates frontmatter `redirectFrom` entries so static redirect pages can be generated.
75
78
 
76
79
  ## Key Components
77
80
 
@@ -95,9 +95,38 @@ bun test # Run all tests
95
95
  bun run test:unit # Run unit tests
96
96
  bun run test:int # Run integration tests
97
97
  bun run test:e2e # Run end-to-end tests
98
+ bun run test:mobile # Run Playwright mobile compatibility tests
98
99
  bun run validate # Lint + test + build:dev
99
100
  ```
100
101
 
102
+ ### Mobile Compatibility Tests
103
+
104
+ `bun run test:mobile` uses [Playwright](https://playwright.dev/) to test the site across 17 real-device profiles:
105
+
106
+ - **Apple:** iPhone SE, iPhone 14 Pro, iPhone 14 Pro Max, iPad Mini, iPad Pro 11
107
+ - **Google:** Pixel 5, Pixel 7
108
+ - **Samsung:** Galaxy S8, Galaxy S21
109
+ - **Huawei:** P50 Pro, Mate 60
110
+ - **Xiaomi:** Xiaomi 14, Redmi Note 13
111
+ - **Oppo:** Find X7, Reno 11
112
+ - **Vivo:** X100, Y100
113
+
114
+ **First-time setup** — install the browser binaries once:
115
+
116
+ ```bash
117
+ bunx playwright install chromium webkit
118
+ ```
119
+
120
+ The test server starts automatically (`bun dev`) if one is not already running. Useful commands:
121
+
122
+ ```bash
123
+ bunx playwright test --project="iPhone SE" # Run one device
124
+ bunx playwright test --project="iPhone SE" --headed # Headed (visible) browser
125
+ bunx playwright show-report # Open HTML report
126
+ ```
127
+
128
+ Tests cover: no horizontal overflow, navigation (hamburger on phones, desktop nav on tablets), mobile menu open/close, touch target sizing (≥44px), post sidebar visibility, scroll lock, font sizes, and image overflow.
129
+
101
130
  ## Building
102
131
 
103
132
  ```bash
@@ -0,0 +1,31 @@
1
+ # Troubleshooting
2
+
3
+ ## False-positive Chrome console warnings in dev mode
4
+
5
+ **Related issue:** [#33](https://github.com/hutusi/amytis/issues/33)
6
+
7
+ When running `bun dev` and opening the site in Chrome with certain browser extensions installed, you may see two console messages that look like project bugs:
8
+
9
+ - **Error**: `Content Security Policy of your site blocks the use of eval in JavaScript.`
10
+ - **Warning**: `Deprecated feature used; the Shared Storage API is deprecated and will be removed in a future release.`
11
+
12
+ **These are not bugs in the project.** Investigation confirmed:
13
+
14
+ - The dev server sends no `Content-Security-Policy` header
15
+ - No meta CSP tag exists in the generated HTML
16
+ - No `eval()` or `new Function()` calls exist in the compiled JS chunks
17
+ - No `sharedStorage` references exist anywhere in the project or its dependencies
18
+
19
+ The messages come from **browser extensions** (e.g. uBlock Origin, Privacy Badger) that inject their own CSP headers or access the Shared Storage API internally. Chrome attributes these to "your site" even though the project is not the source.
20
+
21
+ **To verify:** Open `http://localhost:3000` in a Chrome Incognito window with extensions disabled — both messages will be gone.
22
+
23
+ ## AVIF source images cause 404s in production
24
+
25
+ **Related upstream issue:** [Niels-IO/next-image-export-optimizer#263](https://github.com/Niels-IO/next-image-export-optimizer/issues/263)
26
+
27
+ `next-image-export-optimizer` has a bug with AVIF source files when `storePicturesInWEBP=true`. The optimizer writes `.WEBP` output to disk but `ExportedImage` generates `srcset` paths with the original `.AVIF` extension — pointing to files that do not exist, causing 404 errors in production.
28
+
29
+ **Workaround:** Do not use `.avif` as a source format for cover images or any image referenced via `ExportedImage`. Use `.jpg`, `.png`, or `.webp` instead — the optimizer converts these to WebP correctly.
30
+
31
+ AVIF is a great format in general, but this project's static-export image pipeline (`next-image-export-optimizer`) does not handle AVIF source files correctly until the upstream bug is fixed.
package/next.config.ts CHANGED
@@ -3,8 +3,14 @@ import type { NextConfig } from "next";
3
3
  const nextConfig: NextConfig = {
4
4
  /* config options here */
5
5
  reactCompiler: true,
6
- // Set to true so pages export as slug/index.html, which coexists with
7
- // asset directories (slug/images/) and avoids 403 errors on trailing slash
6
+ // Next.js default is false (slug.html), but we use true (slug/index.html)
7
+ // for two reasons:
8
+ // 1. Co-located assets: posts can have a slug/images/ directory alongside
9
+ // slug/index.html. With false, slug.html and slug/ conflict on some
10
+ // static hosts and cause 403 errors.
11
+ // 2. Nginx cosmetics: nginx.conf strips the trailing slash via redirect
12
+ // (/slug/ → /slug) so the visible URL matches the false convention
13
+ // without changing the export format.
8
14
  trailingSlash: true,
9
15
  output: "export",
10
16
  images: {