create-ampless 0.2.0-alpha.9 → 1.0.0-alpha.101

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 (137) hide show
  1. package/README.ja.md +77 -0
  2. package/README.md +6 -3
  3. package/dist/index.js +743 -84
  4. package/dist/templates/_shared/AGENTS.ja.md +94 -0
  5. package/dist/templates/_shared/AGENTS.md +94 -0
  6. package/dist/templates/_shared/README.ja.md +239 -0
  7. package/dist/templates/_shared/README.md +239 -0
  8. package/dist/templates/_shared/RUNBOOK.ja.md +120 -0
  9. package/dist/templates/_shared/RUNBOOK.md +32 -58
  10. package/dist/templates/_shared/THEMES.ja.md +495 -0
  11. package/dist/templates/_shared/THEMES.md +523 -0
  12. package/dist/templates/_shared/amplify/auth/post-confirmation/resource.ts +7 -0
  13. package/dist/templates/_shared/amplify/backend.ts +10 -0
  14. package/dist/templates/_shared/amplify/data/get-media-by-src.js +45 -0
  15. package/dist/templates/_shared/amplify/data/get-published-post.js +9 -12
  16. package/dist/templates/_shared/amplify/data/list-posts-by-tag.js +6 -9
  17. package/dist/templates/_shared/amplify/data/list-published-posts.js +10 -11
  18. package/dist/templates/_shared/amplify/data/resource.custom.ts +1 -2
  19. package/dist/templates/_shared/amplify/data/resource.ts +28 -7
  20. package/dist/templates/_shared/amplify/events/dispatcher/resource.ts +1 -0
  21. package/dist/templates/_shared/amplify/events/processor-trusted/resource.ts +1 -0
  22. package/dist/templates/_shared/amplify/events/processor-untrusted/resource.ts +5 -0
  23. package/dist/templates/_shared/amplify/functions/api-key-renewer/resource.ts +1 -0
  24. package/dist/templates/_shared/amplify/functions/mcp-handler/handler.ts +1 -0
  25. package/dist/templates/_shared/amplify/functions/mcp-handler/resource.ts +12 -0
  26. package/dist/templates/_shared/amplify/functions/plugin-secret-handler/handler.ts +11 -0
  27. package/dist/templates/_shared/amplify/functions/plugin-secret-handler/resource.ts +12 -0
  28. package/dist/templates/_shared/amplify/functions/user-admin/resource.ts +7 -0
  29. package/dist/templates/_shared/amplify/secrets/.gitkeep +0 -0
  30. package/dist/templates/_shared/amplify/secrets/encryption-key.ts +9 -0
  31. package/dist/templates/_shared/app/(admin)/admin/mcp-tokens/page.tsx +5 -0
  32. package/dist/templates/_shared/app/(admin)/admin/plugins/page.tsx +5 -0
  33. package/dist/templates/_shared/app/globals.css +55 -39
  34. package/dist/templates/_shared/app/layout.tsx +51 -16
  35. package/dist/templates/_shared/app/providers.tsx +0 -2
  36. package/dist/templates/_shared/app/raw/[slug]/route.ts +10 -0
  37. package/dist/templates/_shared/app/static/[slug]/[[...path]]/route.ts +14 -0
  38. package/dist/templates/_shared/cms.config.ts +64 -23
  39. package/dist/templates/_shared/components/site-chrome/site-sidebar.tsx +3 -4
  40. package/dist/templates/_shared/components/tag-list.tsx +9 -2
  41. package/dist/templates/_shared/components.json +1 -1
  42. package/dist/templates/_shared/docs/plugin-author-guide.ja.md +934 -0
  43. package/dist/templates/_shared/docs/plugin-author-guide.md +1336 -0
  44. package/dist/templates/_shared/lib/admin.ts +12 -12
  45. package/dist/templates/_shared/lib/ampless.ts +2 -8
  46. package/dist/templates/_shared/lib/amplify.ts +0 -5
  47. package/dist/templates/_shared/package.json +51 -43
  48. package/dist/templates/_shared/plugins/README.ja.md +139 -0
  49. package/dist/templates/_shared/plugins/README.md +143 -0
  50. package/dist/templates/_shared/proxy.ts +23 -8
  51. package/dist/templates/blog/README.ja.md +22 -0
  52. package/dist/templates/blog/README.md +17 -47
  53. package/dist/templates/blog/manifest.ts +1 -1
  54. package/dist/templates/blog/pages/feed.ts +4 -5
  55. package/dist/templates/blog/pages/home.tsx +10 -15
  56. package/dist/templates/blog/pages/post.tsx +42 -14
  57. package/dist/templates/blog/pages/sitemap.ts +4 -5
  58. package/dist/templates/blog/pages/tag.tsx +8 -10
  59. package/dist/templates/blog/tokens.css +26 -40
  60. package/dist/templates/corporate/README.ja.md +18 -0
  61. package/dist/templates/corporate/README.md +12 -14
  62. package/dist/templates/corporate/pages/feed.ts +3 -4
  63. package/dist/templates/corporate/pages/home.tsx +7 -12
  64. package/dist/templates/corporate/pages/post.tsx +20 -14
  65. package/dist/templates/corporate/pages/sitemap.ts +3 -4
  66. package/dist/templates/corporate/pages/tag.tsx +8 -10
  67. package/dist/templates/corporate/tokens.css +17 -39
  68. package/dist/templates/dads/README.ja.md +31 -0
  69. package/dist/templates/dads/README.md +13 -17
  70. package/dist/templates/dads/pages/feed.ts +3 -4
  71. package/dist/templates/dads/pages/home.tsx +7 -12
  72. package/dist/templates/dads/pages/post.tsx +20 -14
  73. package/dist/templates/dads/pages/sitemap.ts +3 -4
  74. package/dist/templates/dads/pages/tag.tsx +8 -10
  75. package/dist/templates/dads/tokens.css +22 -42
  76. package/dist/templates/docs/README.ja.md +24 -0
  77. package/dist/templates/docs/README.md +10 -13
  78. package/dist/templates/docs/pages/feed.ts +3 -4
  79. package/dist/templates/docs/pages/home.tsx +6 -9
  80. package/dist/templates/docs/pages/post.tsx +19 -13
  81. package/dist/templates/docs/pages/sitemap.ts +3 -4
  82. package/dist/templates/docs/pages/tag.tsx +8 -10
  83. package/dist/templates/docs/tokens.css +17 -39
  84. package/dist/templates/landing/README.ja.md +20 -0
  85. package/dist/templates/landing/README.md +14 -19
  86. package/dist/templates/landing/pages/feed.ts +4 -5
  87. package/dist/templates/landing/pages/home.tsx +7 -12
  88. package/dist/templates/landing/pages/post.tsx +20 -14
  89. package/dist/templates/landing/pages/sitemap.ts +3 -4
  90. package/dist/templates/landing/pages/tag.tsx +8 -10
  91. package/dist/templates/landing/tokens.css +17 -39
  92. package/dist/templates/minimal/README.ja.md +14 -0
  93. package/dist/templates/minimal/README.md +9 -47
  94. package/dist/templates/minimal/pages/feed.ts +4 -5
  95. package/dist/templates/minimal/pages/home.tsx +6 -8
  96. package/dist/templates/minimal/pages/post.tsx +19 -12
  97. package/dist/templates/minimal/pages/sitemap.ts +4 -5
  98. package/dist/templates/minimal/pages/tag.tsx +7 -8
  99. package/dist/templates/minimal/tokens.css +17 -39
  100. package/dist/templates/plugin-local/README.md +34 -0
  101. package/dist/templates/plugin-local/index.ts +39 -0
  102. package/dist/templates/plugin-standalone/CHANGELOG.md +1 -0
  103. package/dist/templates/plugin-standalone/README.ja.md +43 -0
  104. package/dist/templates/plugin-standalone/README.md +43 -0
  105. package/dist/templates/plugin-standalone/package.json +47 -0
  106. package/dist/templates/plugin-standalone/src/index.test.ts +16 -0
  107. package/dist/templates/plugin-standalone/src/index.ts +29 -0
  108. package/dist/templates/plugin-standalone/tsconfig.json +16 -0
  109. package/dist/templates/plugin-standalone/tsup.config.ts +8 -0
  110. package/package.json +1 -1
  111. package/dist/templates/_shared/app/(admin)/admin/sites/page.tsx +0 -5
  112. package/dist/templates/_shared/app/site/[siteId]/raw/[slug]/route.ts +0 -5
  113. package/dist/templates/_shared/components/i18n-provider.tsx +0 -15
  114. package/dist/templates/_shared/lib/admin-site-client.ts +0 -10
  115. package/dist/templates/_shared/lib/admin-site.ts +0 -12
  116. package/dist/templates/_shared/lib/amplify-server.ts +0 -7
  117. package/dist/templates/_shared/lib/auth-server.ts +0 -15
  118. package/dist/templates/_shared/lib/cn.ts +0 -5
  119. package/dist/templates/_shared/lib/i18n.ts +0 -35
  120. package/dist/templates/_shared/lib/kv-provider.ts +0 -7
  121. package/dist/templates/_shared/lib/media.ts +0 -6
  122. package/dist/templates/_shared/lib/posts-provider.ts +0 -7
  123. package/dist/templates/_shared/lib/posts-public.ts +0 -27
  124. package/dist/templates/_shared/lib/posts.ts +0 -12
  125. package/dist/templates/_shared/lib/seo.ts +0 -11
  126. package/dist/templates/_shared/lib/site-settings.ts +0 -11
  127. package/dist/templates/_shared/lib/storage.ts +0 -10
  128. package/dist/templates/_shared/lib/theme-actions.ts +0 -5
  129. package/dist/templates/_shared/lib/theme-active.ts +0 -10
  130. package/dist/templates/_shared/lib/theme-config.ts +0 -12
  131. package/dist/templates/_shared/lib/upload.ts +0 -6
  132. /package/dist/templates/_shared/app/{site/[siteId]/[slug] → [slug]}/page.tsx +0 -0
  133. /package/dist/templates/_shared/app/{site/[siteId]/feed.xml → feed.xml}/route.ts +0 -0
  134. /package/dist/templates/_shared/app/{site/[siteId]/og → og}/[slug]/route.ts +0 -0
  135. /package/dist/templates/_shared/app/{site/[siteId]/page.tsx → page.tsx} +0 -0
  136. /package/dist/templates/_shared/app/{site/[siteId]/sitemap.xml → sitemap.xml}/route.ts +0 -0
  137. /package/dist/templates/_shared/app/{site/[siteId]/tag → tag}/[tag]/page.tsx +0 -0
@@ -0,0 +1,523 @@
1
+ > 日本語版: [THEMES.ja.md](./THEMES.ja.md)
2
+ >
3
+ # THEMES.md
4
+
5
+ Working guide for customizing themes in an ampless project. Written
6
+ for AI coding agents and humans alike — keep it as your day-to-day
7
+ reference when designing or restyling a site.
8
+
9
+ All theme-related instructions live here. `AGENTS.md` / `AGENTS.ja.md`
10
+ deliberately keep only a one-line pointer back to this file.
11
+
12
+ ## Ground rules
13
+
14
+ - Never edit the official themes directly.
15
+ - Keep customizations inside `themes/my-*/`.
16
+ - The shared shell (`app/`, `components/`, `lib/`) is touched only when
17
+ the change genuinely can't be solved inside a theme.
18
+ - `themes-registry.ts` is auto-generated — don't hand-edit it.
19
+ - UI / theme changes are NOT done when type-checks pass. Always verify
20
+ in a browser.
21
+
22
+ ## Picking a base theme
23
+
24
+ Theme customization in ampless starts by copying an existing theme
25
+ into `themes/my-*/`. Deciding "which theme to start from" first keeps
26
+ the scope small — much smaller than deciding "what to build" first.
27
+
28
+ ### `blog`
29
+
30
+ Personal blogs, journals, technical notes, short news commentary —
31
+ anything chronological where individual posts are the primary unit.
32
+
33
+ Out of the box:
34
+
35
+ - Post feed on the home page.
36
+ - Per-post detail page.
37
+ - Per-tag archive page.
38
+ - Header / footer nav.
39
+ - Wide tuning surface for colors, fonts, radii, pinned posts, etc.
40
+
41
+ Good fits:
42
+
43
+ - Personal-media feel.
44
+ - Adjusting the density of the post list.
45
+ - Strengthening typography on the detail page.
46
+ - Designing Markdown elements (tables, code, blockquotes) as part of
47
+ the reading experience.
48
+
49
+ When in doubt, start from `blog`.
50
+
51
+ ### `minimal`
52
+
53
+ Restrained, small-surface blog.
54
+
55
+ Out of the box:
56
+
57
+ - Few manifest customization fields.
58
+ - Minimal chrome.
59
+ - Design steps back so the posts take the lead.
60
+
61
+ Good fits:
62
+
63
+ - Light color / radius tweaks only.
64
+ - Layout largely untouched.
65
+ - Low-content sites.
66
+
67
+ If you want to redesign meaningfully, start from `blog` instead.
68
+
69
+ ### `landing`
70
+
71
+ Single-page introduction sites.
72
+
73
+ Out of the box:
74
+
75
+ - Hero-centric layout.
76
+ - CTA button.
77
+ - Optional latest-posts strip.
78
+ - Site description and primary CTAs front and center.
79
+
80
+ Good fits:
81
+
82
+ - Products, events, portfolios, shop introductions.
83
+ - Sites that prioritize the first impression over a long post list.
84
+ - Letting an AI design the hero / CTA / feature sections.
85
+
86
+ ### `corporate`
87
+
88
+ Company sites, firms, organizations.
89
+
90
+ Out of the box:
91
+
92
+ - Restrained hero.
93
+ - News / announcements list.
94
+ - Substantial header / footer.
95
+ - Footer disclaimer slot.
96
+
97
+ Good fits:
98
+
99
+ - Putting company overview / services up front.
100
+ - Managing news and announcements as posts.
101
+ - Trust, readability, maintainability as priorities.
102
+
103
+ ### `docs`
104
+
105
+ Documentation sites.
106
+
107
+ Out of the box:
108
+
109
+ - Sidebar-driven navigation.
110
+ - Putting `tag:<name>` into a sidebar entry auto-expands the matching
111
+ post list.
112
+ - Structure tuned for code fonts and technical writing.
113
+
114
+ Good fits:
115
+
116
+ - Help docs, specifications, developer reference.
117
+ - Categorizing posts by tag and surfacing them in the sidebar.
118
+ - Heavy use of Markdown code blocks and tables.
119
+
120
+ ### `dads`
121
+
122
+ Public-sector sites following Japan's Digital Agency Design System.
123
+
124
+ Out of the box:
125
+
126
+ - High contrast.
127
+ - Accessibility-first.
128
+ - Restrained decoration.
129
+ - Built on `@digital-go-jp/tailwind-theme-plugin`.
130
+
131
+ Good fits:
132
+
133
+ - Government, municipal, public-sector information sites.
134
+ - Compliance and readability prioritized over a distinctive look.
135
+
136
+ Caveats:
137
+
138
+ - Reskinning to non-DADS palettes erodes the "DADS-compliant" claim.
139
+ - For public-sector work, don't take AI-suggested decorative changes
140
+ at face value — keep accessibility first.
141
+
142
+ ## Standard workflow
143
+
144
+ 1. Copy an official theme.
145
+
146
+ ```bash
147
+ npm run copy-theme blog my-blog
148
+ ```
149
+
150
+ The `my-` prefix marks the copy as user-owned;
151
+ `npm run update-ampless` leaves anything under `themes/my-*/` alone.
152
+
153
+ 2. Implement inside the theme first.
154
+
155
+ In priority order:
156
+
157
+ - `themes/my-blog/tokens.css` — colors, fonts, spacing, rules,
158
+ Markdown body styling.
159
+ - `themes/my-blog/manifest.ts` — fields exposed to the admin UI.
160
+ - `themes/my-blog/pages/` — per-route layouts (home / post / tag /
161
+ feed / sitemap).
162
+ - `themes/my-blog/components.tsx` (or similar) — shared UI used
163
+ only within this theme. Header, footer, wordmark, and such can
164
+ live here.
165
+
166
+ 3. Activate the theme.
167
+
168
+ Open `/admin/sites/<siteId>/theme`, select `my-blog`, save. Theme
169
+ switching is a runtime setting — no redeploy required.
170
+
171
+ 4. Verify.
172
+
173
+ ```bash
174
+ npm run dev
175
+ npx tsc --noEmit
176
+ npm run build
177
+ npm run lint
178
+ ```
179
+
180
+ If `npm run lint` is misaligned with the project's Next.js version,
181
+ say so — but type-check, build, and browser verification still all
182
+ have to pass.
183
+
184
+ ## Design before you implement
185
+
186
+ Before changing a theme, decide in this order:
187
+
188
+ 1. **Site role.** Personal blog? Technical notes? News commentary?
189
+ Company site? Documentation? Landing page?
190
+ 2. **Reading mode.** Slow reading of long-form posts? Fast scanning
191
+ of a list? Strong hero impression? Search / tag discovery?
192
+ 3. **Screen inventory.** At minimum, think through:
193
+ - home
194
+ - post detail
195
+ - tag / archive
196
+ - empty state
197
+ - mobile home
198
+ - mobile detail
199
+ 4. **Change surface.** Is `tokens.css` enough? Does `pages/` structure
200
+ change? Do you need a new theme-local component?
201
+ 5. **Markdown.** For blog or docs themes, Markdown elements are part
202
+ of the design — not afterthought.
203
+
204
+ ### Order of operations
205
+
206
+ First, change what `tokens.css` can change:
207
+
208
+ - Colors
209
+ - Fonts
210
+ - Backgrounds
211
+ - Rules
212
+ - Spacing
213
+ - Prose / Markdown
214
+ - Responsive sizing
215
+
216
+ Then change `pages/`:
217
+
218
+ - Home structure.
219
+ - Post-list density.
220
+ - Post-detail spacing and meta.
221
+ - Tag / archive presentation.
222
+ - Empty state.
223
+
224
+ Last, extract repeated UI into theme-local components like
225
+ `components.tsx`.
226
+
227
+ Reach for shared `components/` or `app/` only when the change has to
228
+ be used across multiple themes.
229
+
230
+ ## Working without Claude Design
231
+
232
+ Read the existing theme first. Don't restart from scratch.
233
+
234
+ What to read:
235
+
236
+ - `tokens.css` token structure.
237
+ - Public fields in `manifest.ts`.
238
+ - Data fetching and rendering responsibilities in `pages/home.tsx`,
239
+ `pages/post.tsx`, `pages/tag.tsx`.
240
+ - `themes/<official-name>/README.*` if present — theme-specific intent.
241
+
242
+ How to proceed:
243
+
244
+ 1. Decompose the requirements into "reading experience", "information
245
+ density", "brand feel", and "content types".
246
+ 2. Set the broad direction in `tokens.css`.
247
+ 3. Change `pages/` only where needed.
248
+ 4. Extract repeated chrome into theme-local components.
249
+ 5. Always include Markdown elements in the design.
250
+
251
+ Markdown elements to check:
252
+
253
+ - Headings `h1` / `h2` / `h3`
254
+ - Paragraphs
255
+ - Lists
256
+ - Tables
257
+ - Blockquotes
258
+ - Inline code
259
+ - Code blocks
260
+ - Images
261
+ - Links
262
+
263
+ For reading-focused sites, body legibility is the top priority.
264
+ Express the design through the framing — spacing, rules, navigation,
265
+ list density, meta typography — not the body itself.
266
+
267
+ ## Using AI for theme customization
268
+
269
+ Don't treat AI as a single "implement everything" tool. Accuracy goes
270
+ up when you split the work across roles.
271
+
272
+ A workable split:
273
+
274
+ - **Design exploration**: Claude Design, ChatGPT, image generation —
275
+ produce a direction.
276
+ - **Implementation plan**: Codex / Claude Code reads the existing
277
+ theme and decides which files reflect the plan.
278
+ - **Implementation**: edits inside the theme.
279
+ - **Verification**: Desktop / Mobile browser screenshots.
280
+ - **Polish**: overflow, empty states, Markdown, long titles.
281
+
282
+ ### What to feed the AI
283
+
284
+ When you ask an AI to draft a theme, include:
285
+
286
+ - Site name.
287
+ - Site content.
288
+ - Audience.
289
+ - Primary post types.
290
+ - Vibes to avoid.
291
+ - Reference sites or screenshots.
292
+ - That both Desktop and Mobile are required.
293
+ - That home / archive / detail / empty-state are all required.
294
+ - That Markdown — tables, lists, code, blockquotes — is in scope.
295
+
296
+ Example:
297
+
298
+ ```text
299
+ Design a theme for ishinao.net, a personal blog. The content is
300
+ day-to-day life, technical notes, and short news commentary.
301
+ Body legibility is the top priority because this is a reading site,
302
+ but the home, archive, header, and meta deserve real design.
303
+ Produce Top-empty, Top-with-content, Archive, and Detail for both
304
+ Desktop and Mobile. Markdown tables, lists, blockquotes, and code
305
+ blocks should sit cleanly inside the theme's world.
306
+ ```
307
+
308
+ ### Don't trust AI output verbatim
309
+
310
+ Common gaps in AI-produced themes:
311
+
312
+ - Desktop looks great; Mobile breaks.
313
+ - Hero is polished but post detail looks generic.
314
+ - Decoration wins over body legibility.
315
+ - Markdown tables / code aren't designed.
316
+ - No empty state.
317
+ - Long real-world Japanese titles overflow.
318
+ - Spacing / type sizes drift between mockup and implementation.
319
+
320
+ Treat AI output as a **visual spec**, not finished code.
321
+
322
+ ## Using Claude Design
323
+
324
+ Claude Design output HTML is usually a collection of artboards across
325
+ multiple screens, not a single finished site. Don't port the HTML
326
+ verbatim — extract the design intent per screen and map it onto the
327
+ ampless theme structure.
328
+
329
+ ### Reading the output
330
+
331
+ First, classify the artboards by screen from the HTML or screenshots:
332
+
333
+ - Desktop / Top empty
334
+ - Desktop / Top with content
335
+ - Desktop / Archive list
336
+ - Desktop / Detail
337
+ - Mobile / Top
338
+ - Mobile / Archive
339
+ - Mobile / Detail
340
+
341
+ Then pull the shared tokens out of each screen:
342
+
343
+ - Background color
344
+ - Accent color
345
+ - Rule color
346
+ - Font family
347
+ - Heading size relationships
348
+ - Body line-height
349
+ - List density
350
+ - Spacing unit
351
+ - Header / footer chrome
352
+ - Mobile widths, padding, line breaks, info-elision rules
353
+
354
+ Don't implement from a single artboard in isolation. Look at Desktop
355
+ and Mobile together to see how the same UI transforms.
356
+
357
+ ### Mapping to ampless
358
+
359
+ Map Claude Design screens onto the ampless theme structure:
360
+
361
+ - Top / Home → `themes/my-blog/pages/home.tsx`
362
+ - Detail / Article → `themes/my-blog/pages/post.tsx`
363
+ - Archive / Tag / List → `themes/my-blog/pages/tag.tsx`
364
+ - Shared header / wordmark / footer →
365
+ `themes/my-blog/components.tsx`
366
+ - Colors, fonts, grid, prose, responsive → `themes/my-blog/tokens.css`
367
+ - Values exposed in the admin UI → `themes/my-blog/manifest.ts`
368
+
369
+ Don't copy generated code or inline styles wholesale. What you need
370
+ is the design rules, not the implementation.
371
+
372
+ ### Implementation tips
373
+
374
+ Extract by hand from the Claude Design HTML:
375
+
376
+ - Screen types.
377
+ - Shared tokens.
378
+ - Layout grid.
379
+ - Heading size scale.
380
+ - List row height, rule treatment, meta position.
381
+ - Mobile elision / vertical stacking / size changes.
382
+ - Empty-state handling.
383
+
384
+ On the ampless side, slot those into:
385
+
386
+ - Shared design language → `tokens.css`
387
+ - Screen structure → `pages/*.tsx`
388
+ - Repeating UI → theme-local `components.tsx`
389
+ - Admin-editable values → `manifest.ts`
390
+
391
+ If a Claude Design board carries multiple states, note them first.
392
+ Example:
393
+
394
+ ```text
395
+ Final - ishinao.net theme
396
+ - Top empty
397
+ - Top with content
398
+ - Archive list
399
+ - Detail editorial body
400
+ - Mobile top empty
401
+ - Mobile top with content
402
+ - Mobile archive
403
+ - Mobile detail
404
+ ```
405
+
406
+ Treat the list as your implementation checklist.
407
+
408
+ ### Claude Design reflection checklist
409
+
410
+ - Implemented both Desktop and Mobile?
411
+ - Handled both empty and populated states?
412
+ - Same design language flows through list / detail / tag pages?
413
+ - Wordmark, nav, meta, rules, background treated consistently?
414
+ - No horizontal scroll or text overflow on mobile?
415
+ - Long Japanese titles don't break the layout?
416
+ - 1 / many / no-tag / multi-tag posts all render cleanly?
417
+ - Markdown tables / code blocks don't float outside the theme world?
418
+
419
+ ## Using other AI tools (no Claude Design)
420
+
421
+ You can still get a lot out of AI without Claude Design.
422
+
423
+ ### Ask in text
424
+
425
+ Have the AI write a design spec first:
426
+
427
+ ```text
428
+ Based on the blog theme, draft a theme spec for a personal technical
429
+ blog. Output is a written spec — not code — split across tokens,
430
+ home, post detail, tag archive, mobile rules, and Markdown styling.
431
+ Body legibility is the top priority; decoration lives in the chrome
432
+ and lists.
433
+ ```
434
+
435
+ Then hand the spec to Codex / Claude Code for implementation.
436
+
437
+ ### Use reference images / screenshots
438
+
439
+ If you have reference imagery, have the AI extract:
440
+
441
+ - Colors.
442
+ - Font vibes.
443
+ - Spacing.
444
+ - Rules.
445
+ - Information density.
446
+ - Desktop / Mobile differences.
447
+ - What should land in `tokens.css`.
448
+ - Whether `pages/` structure has to change.
449
+
450
+ ### Asking AI to implement
451
+
452
+ Phrase the implementation ask narrowly:
453
+
454
+ ```text
455
+ Edit only themes/my-blog. Don't touch shared app / components / lib.
456
+ Read the existing theme first to learn the tokens.css / pages /
457
+ manifest responsibilities. Verify Desktop 1440px and Mobile 390px in
458
+ the browser. Style Markdown tables, lists, blockquotes, and code
459
+ blocks consistently with the theme.
460
+ ```
461
+
462
+ A vague "make it look good" prompt is how you end up with shared
463
+ files edited and Mobile forgotten.
464
+
465
+ ## Browser verification
466
+
467
+ UI / theme changes need a real dev server. Open the actual pages.
468
+
469
+ ```bash
470
+ npm run dev
471
+ ```
472
+
473
+ Representative widths:
474
+
475
+ - Desktop: `1440 x 1100`
476
+ - Mobile: `390 x 844`
477
+
478
+ When taking screenshots, append a query string to bypass any
479
+ intermediate cache:
480
+
481
+ ```text
482
+ http://localhost:3000/?v=theme-check-1
483
+ ```
484
+
485
+ What to look for:
486
+
487
+ - First impression matches the reference direction.
488
+ - Text doesn't overflow its parent.
489
+ - No horizontal scroll on mobile.
490
+ - Background, rules, spacing, and type scale all feel right at the
491
+ width.
492
+ - Body contrast and line-height are comfortable to read.
493
+ - Empty link / tag / nav / footer configurations don't leave dead
494
+ chrome behind.
495
+
496
+ ## Theme-local components
497
+
498
+ Theme-specific shared UI belongs in the theme, e.g.
499
+ `themes/my-blog/components.tsx`.
500
+
501
+ Good candidates:
502
+
503
+ - Header
504
+ - Footer
505
+ - Wordmark
506
+ - Post row
507
+ - Meta block
508
+ - Theme-specific decorative UI
509
+
510
+ Put something in shared `components/` only when it has to be reused
511
+ across multiple themes or by the admin UI.
512
+
513
+ ## Common failure modes
514
+
515
+ - Implementing only the first Claude Design thumbnail you opened.
516
+ - Hugging Desktop and missing the Mobile artboards.
517
+ - Editing shared `components/` for a change `tokens.css` could have
518
+ handled.
519
+ - Editing the official `themes/blog/` directly.
520
+ - Leaving Markdown tables, blockquotes, and code blocks unstyled.
521
+ - Calling a change "done" because the build passed, with no browser
522
+ check.
523
+ - Empty footer / nav configurations still rendering empty chrome.
@@ -3,4 +3,11 @@ import { defineFunction } from '@aws-amplify/backend'
3
3
  export const postConfirmation = defineFunction({
4
4
  name: 'post-confirmation',
5
5
  entry: './handler.ts',
6
+ runtime: 22,
7
+ // Auth trigger — assign to the auth nested stack so CFN doesn't
8
+ // place it in a separate `function` stack that ends up in a
9
+ // circular dependency loop with auth (which references this
10
+ // function) and storage/data (which auth happens to reach via
11
+ // cross-stack refs the function adds).
12
+ resourceGroupName: 'auth',
6
13
  })
@@ -8,8 +8,15 @@ import { eventDispatcher } from './events/dispatcher/resource.js'
8
8
  import { processorTrusted } from './events/processor-trusted/resource.js'
9
9
  import { processorUntrusted } from './events/processor-untrusted/resource.js'
10
10
  import { apiKeyRenewer } from './functions/api-key-renewer/resource.js'
11
+ import { mcpHandler } from './functions/mcp-handler/resource.js'
11
12
  import { userAdmin } from './functions/user-admin/resource.js'
13
+ import { pluginSecretHandler } from './functions/plugin-secret-handler/resource.js'
12
14
  import { customizeBackend } from './backend.custom.js'
15
+ // Plugin secret encryption key (Phase 6a v2.2).
16
+ // Generate with: npx create-ampless setup-encryption-key
17
+ // The key lives in amplify/secrets/encryption-key.ts (gitignore it for
18
+ // public repos; safe to commit for private repos).
19
+ import { PLUGIN_SECRET_ENCRYPTION_KEY } from './secrets/encryption-key.js'
13
20
 
14
21
  // `defineAmplessBackend` provisions auth, data, storage, the event
15
22
  // system (DynamoDB Streams → SQS-trusted / SQS-untrusted → trust_level
@@ -27,7 +34,10 @@ const backend = defineAmplessBackend({
27
34
  processorTrusted,
28
35
  processorUntrusted,
29
36
  apiKeyRenewer,
37
+ mcpHandler,
30
38
  userAdmin,
39
+ pluginSecretHandler,
40
+ pluginSecretEncryptionKey: PLUGIN_SECRET_ENCRYPTION_KEY,
31
41
  })
32
42
 
33
43
  // Run user-defined customizations after baseline wiring. `backend.custom.ts`
@@ -0,0 +1,45 @@
1
+ import { util } from '@aws-appsync/utils'
2
+
3
+ // AppSync JS resolver: returns a single Media row by S3 key.
4
+ //
5
+ // Reads the Media table's `bySrc` GSI: PK = src. The src is the full
6
+ // S3 key (`public/media/...`) and is unique across the table (uploads
7
+ // use a timestamp-prefixed naming scheme), so this is an O(1) PK
8
+ // Query with `limit: 1` — no scan, no filter.
9
+ //
10
+ // Returns null when no row matches (orphan / legacy assets); the
11
+ // caller (the `/api/media/...` route handler) falls back to an
12
+ // Amplify SSR HEAD via `getProperties` in that case.
13
+ //
14
+ // Authorisation is enforced by AppSync (`allow.publicApiKey()` on
15
+ // the schema declaration). The resolver itself only encodes the
16
+ // partition condition.
17
+ export function request(ctx) {
18
+ const src = ctx.args.src
19
+ return {
20
+ operation: 'Query',
21
+ index: 'bySrc',
22
+ query: {
23
+ expression: '#src = :src',
24
+ expressionNames: { '#src': 'src' },
25
+ expressionValues: util.dynamodb.toMapValues({ ':src': src }),
26
+ },
27
+ limit: 1,
28
+ }
29
+ }
30
+
31
+ export function response(ctx) {
32
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type)
33
+ const items = ctx.result.items ?? []
34
+ if (items.length === 0) return null
35
+ const item = items[0]
36
+ // Project explicitly onto the PublicMedia shape — drop mediaId /
37
+ // delivery / anything else that might be added to the Media model
38
+ // later so guests never see fields they shouldn't.
39
+ return {
40
+ src: item.src,
41
+ size: item.size ?? null,
42
+ mimeType: item.mimeType ?? null,
43
+ metadata: item.metadata ?? null,
44
+ }
45
+ }
@@ -2,24 +2,21 @@ import { util } from '@aws-appsync/utils'
2
2
 
3
3
  // AppSync JS resolver: returns a single published post by slug.
4
4
  //
5
- // Reads the `bySiteIdSlug` GSI: PK = `${siteId}#${slug}`. A given
6
- // (site, slug) tuple identifies at most one row, so this is an O(1)
7
- // PK Query — no scan, no filter, no per-partition limit issues.
5
+ // Reads the `bySlug` GSI: PK = slug. A slug identifies at most one row
6
+ // (uniqueness enforced at the admin form level), so this is an O(1) PK
7
+ // Query — no scan, no filter, no per-partition limit issues.
8
8
  //
9
- // Drafts are dropped in the response handler. The admin form
10
- // enforces a unique slug per site at the application layer, but if
11
- // somehow draft + published share a slug we prefer the published row.
9
+ // Drafts are dropped in the response handler. If draft + published
10
+ // somehow share a slug we prefer the published row.
12
11
  export function request(ctx) {
13
- const siteId = ctx.args.siteId ?? 'default'
14
12
  const slug = ctx.args.slug
15
- const partition = `${siteId}#${slug}`
16
13
  return {
17
14
  operation: 'Query',
18
- index: 'bySiteIdSlug',
15
+ index: 'bySlug',
19
16
  query: {
20
- expression: '#siteIdSlug = :siteIdSlug',
21
- expressionNames: { '#siteIdSlug': 'siteIdSlug' },
22
- expressionValues: util.dynamodb.toMapValues({ ':siteIdSlug': partition }),
17
+ expression: '#slug = :slug',
18
+ expressionNames: { '#slug': 'slug' },
19
+ expressionValues: util.dynamodb.toMapValues({ ':slug': slug }),
23
20
  },
24
21
  limit: 5,
25
22
  }
@@ -2,23 +2,21 @@ import { util } from '@aws-appsync/utils'
2
2
 
3
3
  // AppSync JS resolver: list published posts for a given tag, newest first.
4
4
  // Reads the denormalized PostTag table where:
5
- // PK = `${siteId}#${tag}`
5
+ // PK = tag
6
6
  // SK = `${publishedAt}#${postId}` (so descending SK = newest first)
7
7
  //
8
8
  // Authorization is enforced by AppSync; the resolver itself only encodes
9
- // the tag/site partition condition. Drafts never appear here because the
9
+ // the tag partition condition. Drafts never appear here because the
10
10
  // admin client only writes PostTag rows for posts whose status is
11
11
  // 'published'.
12
12
  export function request(ctx) {
13
- const { siteId = 'default', tag, limit, nextToken } = ctx.args
13
+ const { tag, limit, nextToken } = ctx.args
14
14
  return {
15
15
  operation: 'Query',
16
16
  query: {
17
- expression: '#siteIdTag = :siteIdTag',
18
- expressionNames: { '#siteIdTag': 'siteIdTag' },
19
- expressionValues: util.dynamodb.toMapValues({
20
- ':siteIdTag': `${siteId}#${tag}`,
21
- }),
17
+ expression: '#tag = :tag',
18
+ expressionNames: { '#tag': 'tag' },
19
+ expressionValues: util.dynamodb.toMapValues({ ':tag': tag }),
22
20
  },
23
21
  scanIndexForward: false, // newest first (SK descends)
24
22
  limit: limit ?? 20,
@@ -33,7 +31,6 @@ export function response(ctx) {
33
31
  // PublicPost shape `listPublishedPosts` returns; the detail view should
34
32
  // call `getPublishedPost(slug)` for the full body.
35
33
  const items = (ctx.result.items ?? []).map((row) => ({
36
- siteId: row.siteId,
37
34
  postId: row.postId,
38
35
  slug: row.slug,
39
36
  title: row.title,