@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.
- package/AGENTS.md +7 -4
- package/CHANGELOG.md +23 -0
- package/CLAUDE.md +11 -0
- package/README.md +53 -5
- package/README.zh.md +36 -6
- package/bun.lock +9 -0
- package/content/posts/2026-01-12-the-art-of-algorithms.mdx +3 -0
- package/content/posts/2026-01-21-kitchen-sink/index.mdx +2 -2
- package/content/posts/multimedia-showcase/index.mdx +4 -6
- package/content/series/markdown-showcase/syntax-highlighting.mdx +1 -1
- package/content/series/modern-web-dev/index.mdx +13 -0
- package/content/series/nextjs-deep-dive/01-getting-started.mdx +1 -1
- package/docs/ARCHITECTURE.md +3 -0
- package/docs/CONTRIBUTING.md +29 -0
- package/docs/TROUBLESHOOTING.md +31 -0
- package/next.config.ts +8 -2
- package/nginx.conf.example +156 -0
- package/package.json +6 -2
- package/packages/create-amytis/src/index.test.ts +2 -2
- package/playwright.config.ts +199 -0
- package/public/images/vibrant-waves.jpg +0 -0
- package/scripts/add-series-redirects.ts +192 -0
- package/scripts/import-obsidian.ts +319 -0
- package/site.config.example.ts +17 -2
- package/site.config.ts +17 -2
- package/src/app/[slug]/[postSlug]/page.tsx +64 -10
- package/src/app/[slug]/page.tsx +48 -10
- package/src/app/flows/[year]/[month]/[day]/page.tsx +7 -1
- package/src/app/layout.tsx +1 -2
- package/src/app/notes/[slug]/page.tsx +31 -7
- package/src/app/page.tsx +6 -6
- package/src/app/posts/[slug]/page.tsx +36 -4
- package/src/app/series/[slug]/page/[page]/page.tsx +6 -5
- package/src/app/series/[slug]/page.tsx +8 -7
- package/src/app/tags/[tag]/page.tsx +6 -1
- package/src/components/CoverImage.tsx +3 -1
- package/src/components/CuratedSeriesSection.tsx +5 -4
- package/src/components/FeaturedStoriesSection.tsx +12 -11
- package/src/components/Footer.tsx +1 -1
- package/src/components/Hero.tsx +2 -2
- package/src/components/KatexStyles.tsx +8 -0
- package/src/components/LatestWritingSection.tsx +5 -9
- package/src/components/MarkdownRenderer.test.tsx +3 -0
- package/src/components/MarkdownRenderer.tsx +7 -2
- package/src/components/Navbar.tsx +11 -10
- package/src/components/PostList.tsx +7 -7
- package/src/components/PostNavigation.tsx +39 -14
- package/src/components/PostSidebar.tsx +39 -19
- package/src/components/RecentNotesSection.tsx +3 -6
- package/src/components/RedirectPage.tsx +27 -0
- package/src/components/SelectedBooksSection.tsx +4 -4
- package/src/components/SeriesCatalog.tsx +7 -4
- package/src/components/SeriesList.tsx +38 -22
- package/src/components/TagSidebar.tsx +3 -3
- package/src/components/TagsIndexClient.tsx +1 -1
- package/src/i18n/translations.ts +4 -0
- package/src/layouts/BookLayout.tsx +11 -0
- package/src/layouts/PostLayout.tsx +36 -18
- package/src/layouts/SimpleLayout.tsx +13 -1
- package/src/lib/comments.test.ts +50 -0
- package/src/lib/comments.ts +25 -0
- package/src/lib/markdown.ts +132 -23
- package/src/lib/remark-wikilinks.test.ts +86 -0
- package/src/lib/remark-wikilinks.ts +1 -1
- package/src/lib/urls.ts +52 -1
- package/tests/e2e/mobile/mobile-compat.spec.ts +407 -0
- package/tests/integration/collections.test.ts +129 -0
- package/tests/tooling/add-series-redirects.test.ts +110 -0
- package/tests/tooling/import-obsidian.test.ts +96 -0
- package/tests/unit/static-params.test.ts +99 -4
- package/tests/unit/urls.test.ts +30 -0
- 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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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/` (
|
|
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
|
-
|
|
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
|
|
161
|
-
- **Flows
|
|
162
|
-
- **Series**:创建 `content/series/<slug>/index.mdx`
|
|
163
|
-
- **Books
|
|
164
|
-
- **Notes
|
|
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
|

|
|
144
144
|
|
|
145
|
-
### Vibrant Waves (
|
|
145
|
+
### Vibrant Waves (JPG format)
|
|
146
146
|
|
|
147
|
-

|
|
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
|
-
|
|
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.
|
|
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.
|
|
6
|
+
coverImage: "/images/vibrant-waves.jpg"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Getting Started
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -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
|
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -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
|
-
//
|
|
7
|
-
//
|
|
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: {
|