[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6D2G14PY5PTSA05N3FR6GQJ":3,"skill-same:01M6D2G14PY5PTSA05N3FR6GQJ":72},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":49,"commitSha":50,"skillDirectory":51,"forksCount":52,"repositoryHtmlUrl":53,"repositoryHomepageUrl":54,"ownerHtmlUrl":55,"ownerBlogUrl":54,"ownerDescription":56,"updatedAt":57,"install":58,"resources":71},"01M6D2G14PY5PTSA05N3FR6GQJ","ui-stack-astro",null,"Building with Astro: when it fits, zero JavaScript by default, islands with client directives, content collections with schemas, layouts and components, images with astro:assets, view transitions, integrations for React, Vue or Svelte, server output and adapters, and build checks. Read when the project uses Astro or is a content site choosing a stack.","enowdev\u002Fenowxcli","enowdev","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F179420349?v=4","01M6D1MVX2KC0X3Z9NESE7P22K","Enow Dev.","01M6D1MW1FMZGBDKWAJY4GQX81",67,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-astro\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"ui stack astro","ui","astro","stack","read","collections","svelte","project","transitions","build","default","images","view","javascript","integrations","ZeRO","server","vue","react","components","2026-11-10 10:12:30",false,"---\nname: ui-stack-astro\ndescription: \"Building with Astro: when it fits, zero JavaScript by default, islands with client directives, content collections with schemas, layouts and components, images with astro:assets, view transitions, integrations for React, Vue or Svelte, server output and adapters, and build checks. Read when the project uses Astro or is a content site choosing a stack.\"\n---\n\n# Astro\n\nAstro used by habit becomes a single-page app in a wrapper: `client:load` on\nevery component, content typed into arrays, full-size images in `public\u002F`,\na menu script that dies after the first client-side navigation. Its point\nis the reverse: HTML from the server, JavaScript only where a part must\nbehave. This covers Astro 6 and 7 (current in 2026, Node 22.12 or later);\ncheck `package.json`, as Astro 5 still had `Astro.glob` and\n`\u003CViewTransitions \u002F>`.\n\n## 1. When it fits\n\n- Content read more than operated: docs, blogs, product sites, portfolios,\n  a local business, a catalogue. Not an app behind a sign-in with shared\n  state on every screen: Vite with React, SvelteKit or Next.js instead\n  (`ui-stack-react`, `ui-stack-svelte`, `ui-stack-next`).\n- `npm create astro@latest` starts a project; `npx astro add \u003Cname>`\n  installs an integration and edits the config.\n\n```\nsrc\u002Fpages\u002F           routes: index.astro, blog\u002F[...id].astro, rss.xml.ts, 404.astro\nsrc\u002Flayouts\u002F         Base.astro: \u003Chtml lang>, head, header, footer, \u003Cslot \u002F>\nsrc\u002Fcomponents\u002F      .astro components; islands in their framework (Search.tsx)\nsrc\u002Fcontent\u002F         Markdown, MDX, data; the schemas in src\u002Fcontent.config.ts\nsrc\u002Fassets\u002F          images Astro optimises (public\u002F is served untouched)\n```\n\n## 2. Components\n\n```astro\n---\n\u002F\u002F The frontmatter runs on the server (build or request time), never in the browser.\nimport type { HTMLAttributes } from \"astro\u002Ftypes\";\ninterface Props extends HTMLAttributes\u003C\"a\"> { variant?: \"primary\" | \"quiet\" }\nconst { variant = \"quiet\", class: className, ...rest } = Astro.props;\n---\n\u003Ca class:list={[\"button\", `button--${variant}`, className]} {...rest}>\u003Cslot \u002F>\u003C\u002Fa>\n```\n\n- Named slots (`\u003Cslot name=\"aside\" \u002F>`) with fallback content;\n  `Astro.slots.has(\"aside\")` drops an empty wrapper. `\u003Cstyle>` is scoped to\n  the component; Markdown rendered inside is not its markup, so reach it\n  with `:global()` from a wrapper (`.prose :global(h2)`).\n- Expressions are escaped; `set:html` is not, so it takes only trusted or\n  sanitised HTML. `is:inline` scripts skip bundling: only for tiny head\n  scripts, such as setting the theme before the first paint.\n- Astro 7's compiler errors on unclosed tags and no longer repairs invalid\n  nesting, and its default `compressHTML: \"jsx\"` drops whitespace between\n  inline elements as JSX does: write `{\" \"}` where a space must stay.\n\n## 3. Islands: JavaScript only where it behaves\n\nA framework component with no directive renders to HTML and ships nothing;\na `client:` directive makes it an island that hydrates.\n\n| Directive | Hydrates | For |\n|---|---|---|\n| none | never | anything that only displays |\n| `client:visible` | when scrolled into view | below the fold: a chart, a carousel, comments |\n| `client:idle` | when the browser is idle | low-priority parts already in view |\n| `client:media=\"(max-width: 767px)\"` | when the query matches | a phone-only menu |\n| `client:load` | at once | what the first screen needs now: search, the menu button |\n| `client:only=\"react\"` | in the browser, no server HTML | parts that need `window` (a map), with a fallback |\n\n- The laziest that works (`client:visible={{ rootMargin: \"200px\" }}` starts\n  early); `client:load` everywhere is a single-page app with extra steps.\n  Props are serialised (plain data, no functions); children arrive as HTML.\n  Islands share no context: use Nano Stores (`nanostores` with\n  `@nanostores\u002Freact` or `@nanostores\u002Fvue`).\n- Small behaviour needs no framework. A `\u003Cscript>` in an `.astro` file is\n  bundled, typed and run once as a module; as a custom element it works\n  for every instance and after client-side navigation:\n\n```astro\n\u003Ccopy-code data-code={code}>\u003Cbutton type=\"button\">Copy\u003C\u002Fbutton>\u003C\u002Fcopy-code>\n\u003Cscript>\n  customElements.define(\"copy-code\", class extends HTMLElement {\n    connectedCallback() {\n      const button = this.querySelector(\"button\")!;\n      button.onclick = async () => {\n        await navigator.clipboard.writeText(this.dataset.code ?? \"\");\n        button.textContent = \"Copied\";\n      };\n    }\n  });\n\u003C\u002Fscript>\n```\n\n## 4. Content collections\n\n```ts\n\u002F\u002F src\u002Fcontent.config.ts\nimport { defineCollection } from \"astro:content\";\nimport { glob } from \"astro\u002Floaders\";\nimport { z } from \"astro\u002Fzod\";\n\nconst blog = defineCollection({\n  loader: glob({ pattern: \"**\u002F*.{md,mdx}\", base: \".\u002Fsrc\u002Fcontent\u002Fblog\" }),\n  schema: ({ image }) => z.object({\n    title: z.string().max(70), description: z.string().max(160),\n    published: z.coerce.date(), cover: image().optional(),\n    draft: z.boolean().default(false),\n  }),\n});\nexport const collections = { blog };\n```\n\n```astro\n---\n\u002F\u002F src\u002Fpages\u002Fblog\u002F[...id].astro\nimport { getCollection, render } from \"astro:content\";\nexport async function getStaticPaths() {\n  const posts = await getCollection(\"blog\", ({ data }) => !data.draft);\n  return posts.map((post) => ({ params: { id: post.id }, props: { post } }));\n}\nconst { post } = Astro.props;\nconst { Content } = await render(post);\n---\n\u003Carticle class=\"prose\">\u003Ch1>{post.data.title}\u003C\u002Fh1>\u003CContent \u002F>\u003C\u002Farticle>\n```\n\n- A file that breaks the schema fails the build, naming file and field;\n  types come with it (`CollectionEntry\u003C\"blog\">`). `render()` also returns\n  `headings`; `reference(\"authors\")` links collections; `file()` loads one\n  JSON or YAML file; live collections (Astro 6 and later) load per request.\n- `z` is Zod 4 from `astro\u002Fzod` (`z.email()`, `z.url()`); `Astro.glob` is\n  gone (a collection, or `import.meta.glob`). MDX via `npx astro add mdx`.\n- Shiki highlights Markdown code (`markdown.shikiConfig.themes`, light and\n  dark, `ui-themes`). Astro 7 renders Markdown with Sätteri: remark and\n  rehype plugins need `markdown.processor: unified({ remarkPlugins,\n  rehypePlugins })`, `unified` imported from `@astrojs\u002Fmarkdown-remark`.\n\n## 5. Routing and rendering\n\n- File-based (`src\u002Fpages\u002Fabout.astro` is `\u002Fabout`), `404.astro`, endpoints\n  such as `rss.xml.ts` exporting `GET`. Static by default; a page run per\n  request (search, a signed-in area, a form) needs an adapter (`npx astro\n  add node`, `vercel`, `netlify`, `cloudflare`) and `export const prerender\n  = false`; `output: \"server\"` only when most pages are dynamic.\n- `\u003CRecommendations server:defer>` with a `slot=\"fallback\"` placeholder (a\n  server island) puts one per-person part on an otherwise cached page.\n- `src\u002Fmiddleware.ts` (`defineMiddleware`) for sessions and redirects via\n  `Astro.locals`; Actions (`astro:actions`) for form posts, validated on\n  the server. `astro:env` types the environment; `PUBLIC_` variables ship\n  to every browser, so secrets never carry that prefix.\n\n## 6. Images and fonts\n\n```astro\n---\nimport { Picture } from \"astro:assets\";\nimport shopFront from \"..\u002Fassets\u002Fshop-front.jpg\";\n---\n\u003CPicture src={shopFront} formats={[\"avif\", \"webp\"]} alt=\"The shop front on [street]\"\n  widths={[480, 800, 1200]} sizes=\"(min-width: 1024px) 50vw, 100vw\" priority \u002F>\n```\n\n- Images imported from `src\u002F` are optimised and sized from the file;\n  `public\u002F` is copied untouched; remote ones need `image.remotePatterns`;\n  `alt` is required (empty for decoration). Both are lazy by default:\n  `priority` (eager, high fetch priority) only on the first screen's\n  largest image; `layout=\"constrained\"` writes `srcset` and `sizes`.\n- The Fonts API self-hosts at build: `fonts: [{ provider:\n  fontProviders.fontsource(), name: \"Fraunces\", cssVariable:\n  \"--font-display\" }]` in the config, `\u003CFont cssVariable=\"--font-display\"\n  preload \u002F>` (from `astro:assets`) in the head, preload above the fold only.\n\n## 7. Styles, themes, view transitions\n\n- Tokens as custom properties in one file the base layout imports;\n  Tailwind v4 via `npx astro add tailwind` (tokens in `@theme`,\n  `ui-stack-tailwind`); an `is:inline` head script sets the theme before\n  the first paint (`ui-themes`).\n- Between pages, the browser's own transitions and no script:\n  `@view-transition { navigation: auto; }` inside `@media\n  (prefers-reduced-motion: no-preference)` (Chromium, Safari 18.2 and\n  later; others just load) (`motion-interface`).\n- `\u003CClientRouter \u002F>` (`astro:transitions`) when state must survive\n  navigation (`transition:persist`, `transition:name`); it turns its\n  animations off under reduced motion. Its trap: bundled scripts run once,\n  so page setup goes in `document.addEventListener(\"astro:page-load\",\n  setup)` or in custom elements.\n\n## 8. Head, docs, other frameworks\n\n- A head component: `\u003Ctitle>`, description, canonical\n  (`new URL(Astro.url.pathname, Astro.site)`), Open Graph; `site` in the\n  config (canonical, `@astrojs\u002Fsitemap` and `@astrojs\u002Frss` need it); `lang`\n  on `\u003Chtml>`; `i18n` routing (`frontend-seo`, `i18n`); `prefetch: true`;\n  `security: { csp: true }` for a Content Security Policy.\n- Docs: `npm create astro@latest -- --template starlight` (sidebar, Pagefind\n  search, both themes, code blocks with copy, `:::note` asides, `\u003CTabs\n  syncKey=\"pkg\">`, edit links; `ui-page-docs`), themed through\n  `--sl-color-accent`, `--sl-font` and component overrides.\n- `npx astro add react` (or `vue`, `svelte`): one framework, following its\n  skill (`ui-stack-react`, `ui-stack-vue`, `ui-stack-svelte`).\n\n## Check it\n\n- `npx astro check` (it offers to install `@astrojs\u002Fcheck` and\n  `typescript`): types in `.astro` files, props, collections.\n- `npx astro build` fails on a schema mismatch, a broken import, a missing\n  `alt` or an unclosed tag; in `dist\u002F`, display-only pages load no script.\n- The `preview` tool: `url` `http:\u002F\u002Flocalhost:4321\u002F`, `start` `npm run dev\n  -- --port 4321`. With `\u003CClientRouter \u002F>`, navigate three pages and use\n  each interactive part again. Then `ui_check`.\n\n## Avoid\n\n`client:load` on every component; a whole React app in one island for a\ncontent page; `client:only` for content that belongs in the HTML; content\nin hand-typed arrays; `Astro.glob`; content images in `public\u002F`;\n`set:html` with anything unsanitised; secrets under `PUBLIC_`; `output:\n\"server\"` for a site that could be static; scripts that die after a\nclient-side navigation; a font stylesheet from a CDN blocking first paint.\n","# Astro\n\nAstro used by habit becomes a single-page app in a wrapper: `client:load` on\nevery component, content typed into arrays, full-size images in `public\u002F`,\na menu script that dies after the first client-side navigation. Its point\nis the reverse: HTML from the server, JavaScript only where a part must\nbehave. This covers Astro 6 and 7 (current in 2026, Node 22.12 or later);\ncheck `package.json`, as Astro 5 still had `Astro.glob` and\n`\u003CViewTransitions \u002F>`.\n\n## 1. When it fits\n\n- Content read more than operated: docs, blogs, product sites, portfolios,\n  a local business, a catalogue. Not an app behind a sign-in with shared\n  state on every screen: Vite with React, SvelteKit or Next.js instead\n  (`ui-stack-react`, `ui-stack-svelte`, `ui-stack-next`).\n- `npm create astro@latest` starts a project; `npx astro add \u003Cname>`\n  installs an integration and edits the config.\n\n```\nsrc\u002Fpages\u002F           routes: index.astro, blog\u002F[...id].astro, rss.xml.ts, 404.astro\nsrc\u002Flayouts\u002F         Base.astro: \u003Chtml lang>, head, header, footer, \u003Cslot \u002F>\nsrc\u002Fcomponents\u002F      .astro components; islands in their framework (Search.tsx)\nsrc\u002Fcontent\u002F         Markdown, MDX, data; the schemas in src\u002Fcontent.config.ts\nsrc\u002Fassets\u002F          images Astro optimises (public\u002F is served untouched)\n```\n\n## 2. Components\n\n```astro\n---\n\u002F\u002F The frontmatter runs on the server (build or request time), never in the browser.\nimport type { HTMLAttributes } from \"astro\u002Ftypes\";\ninterface Props extends HTMLAttributes\u003C\"a\"> { variant?: \"primary\" | \"quiet\" }\nconst { variant = \"quiet\", class: className, ...rest } = Astro.props;\n---\n\u003Ca class:list={[\"button\", `button--${variant}`, className]} {...rest}>\u003Cslot \u002F>\u003C\u002Fa>\n```\n\n- Named slots (`\u003Cslot name=\"aside\" \u002F>`) with fallback content;\n  `Astro.slots.has(\"aside\")` drops an empty wrapper. `\u003Cstyle>` is scoped to\n  the component; Markdown rendered inside is not its markup, so reach it\n  with `:global()` from a wrapper (`.prose :global(h2)`).\n- Expressions are escaped; `set:html` is not, so it takes only trusted or\n  sanitised HTML. `is:inline` scripts skip bundling: only for tiny head\n  scripts, such as setting the theme before the first paint.\n- Astro 7's compiler errors on unclosed tags and no longer repairs invalid\n  nesting, and its default `compressHTML: \"jsx\"` drops whitespace between\n  inline elements as JSX does: write `{\" \"}` where a space must stay.\n\n## 3. Islands: JavaScript only where it behaves\n\nA framework component with no directive renders to HTML and ships nothing;\na `client:` directive makes it an island that hydrates.\n\n| Directive | Hydrates | For |\n|---|---|---|\n| none | never | anything that only displays |\n| `client:visible` | when scrolled into view | below the fold: a chart, a carousel, comments |\n| `client:idle` | when the browser is idle | low-priority parts already in view |\n| `client:media=\"(max-width: 767px)\"` | when the query matches | a phone-only menu |\n| `client:load` | at once | what the first screen needs now: search, the menu button |\n| `client:only=\"react\"` | in the browser, no server HTML | parts that need `window` (a map), with a fallback |\n\n- The laziest that works (`client:visible={{ rootMargin: \"200px\" }}` starts\n  early); `client:load` everywhere is a single-page app with extra steps.\n  Props are serialised (plain data, no functions); children arrive as HTML.\n  Islands share no context: use Nano Stores (`nanostores` with\n  `@nanostores\u002Freact` or `@nanostores\u002Fvue`).\n- Small behaviour needs no framework. A `\u003Cscript>` in an `.astro` file is\n  bundled, typed and run once as a module; as a custom element it works\n  for every instance and after client-side navigation:\n\n```astro\n\u003Ccopy-code data-code={code}>\u003Cbutton type=\"button\">Copy\u003C\u002Fbutton>\u003C\u002Fcopy-code>\n\u003Cscript>\n  customElements.define(\"copy-code\", class extends HTMLElement {\n    connectedCallback() {\n      const button = this.querySelector(\"button\")!;\n      button.onclick = async () => {\n        await navigator.clipboard.writeText(this.dataset.code ?? \"\");\n        button.textContent = \"Copied\";\n      };\n    }\n  });\n\u003C\u002Fscript>\n```\n\n## 4. Content collections\n\n```ts\n\u002F\u002F src\u002Fcontent.config.ts\nimport { defineCollection } from \"astro:content\";\nimport { glob } from \"astro\u002Floaders\";\nimport { z } from \"astro\u002Fzod\";\n\nconst blog = defineCollection({\n  loader: glob({ pattern: \"**\u002F*.{md,mdx}\", base: \".\u002Fsrc\u002Fcontent\u002Fblog\" }),\n  schema: ({ image }) => z.object({\n    title: z.string().max(70), description: z.string().max(160),\n    published: z.coerce.date(), cover: image().optional(),\n    draft: z.boolean().default(false),\n  }),\n});\nexport const collections = { blog };\n```\n\n```astro\n---\n\u002F\u002F src\u002Fpages\u002Fblog\u002F[...id].astro\nimport { getCollection, render } from \"astro:content\";\nexport async function getStaticPaths() {\n  const posts = await getCollection(\"blog\", ({ data }) => !data.draft);\n  return posts.map((post) => ({ params: { id: post.id }, props: { post } }));\n}\nconst { post } = Astro.props;\nconst { Content } = await render(post);\n---\n\u003Carticle class=\"prose\">\u003Ch1>{post.data.title}\u003C\u002Fh1>\u003CContent \u002F>\u003C\u002Farticle>\n```\n\n- A file that breaks the schema fails the build, naming file and field;\n  types come with it (`CollectionEntry\u003C\"blog\">`). `render()` also returns\n  `headings`; `reference(\"authors\")` links collections; `file()` loads one\n  JSON or YAML file; live collections (Astro 6 and later) load per request.\n- `z` is Zod 4 from `astro\u002Fzod` (`z.email()`, `z.url()`); `Astro.glob` is\n  gone (a collection, or `import.meta.glob`). MDX via `npx astro add mdx`.\n- Shiki highlights Markdown code (`markdown.shikiConfig.themes`, light and\n  dark, `ui-themes`). Astro 7 renders Markdown with Sätteri: remark and\n  rehype plugins need `markdown.processor: unified({ remarkPlugins,\n  rehypePlugins })`, `unified` imported from `@astrojs\u002Fmarkdown-remark`.\n\n## 5. Routing and rendering\n\n- File-based (`src\u002Fpages\u002Fabout.astro` is `\u002Fabout`), `404.astro`, endpoints\n  such as `rss.xml.ts` exporting `GET`. Static by default; a page run per\n  request (search, a signed-in area, a form) needs an adapter (`npx astro\n  add node`, `vercel`, `netlify`, `cloudflare`) and `export const prerender\n  = false`; `output: \"server\"` only when most pages are dynamic.\n- `\u003CRecommendations server:defer>` with a `slot=\"fallback\"` placeholder (a\n  server island) puts one per-person part on an otherwise cached page.\n- `src\u002Fmiddleware.ts` (`defineMiddleware`) for sessions and redirects via\n  `Astro.locals`; Actions (`astro:actions`) for form posts, validated on\n  the server. `astro:env` types the environment; `PUBLIC_` variables ship\n  to every browser, so secrets never carry that prefix.\n\n## 6. Images and fonts\n\n```astro\n---\nimport { Picture } from \"astro:assets\";\nimport shopFront from \"..\u002Fassets\u002Fshop-front.jpg\";\n---\n\u003CPicture src={shopFront} formats={[\"avif\", \"webp\"]} alt=\"The shop front on [street]\"\n  widths={[480, 800, 1200]} sizes=\"(min-width: 1024px) 50vw, 100vw\" priority \u002F>\n```\n\n- Images imported from `src\u002F` are optimised and sized from the file;\n  `public\u002F` is copied untouched; remote ones need `image.remotePatterns`;\n  `alt` is required (empty for decoration). Both are lazy by default:\n  `priority` (eager, high fetch priority) only on the first screen's\n  largest image; `layout=\"constrained\"` writes `srcset` and `sizes`.\n- The Fonts API self-hosts at build: `fonts: [{ provider:\n  fontProviders.fontsource(), name: \"Fraunces\", cssVariable:\n  \"--font-display\" }]` in the config, `\u003CFont cssVariable=\"--font-display\"\n  preload \u002F>` (from `astro:assets`) in the head, preload above the fold only.\n\n## 7. Styles, themes, view transitions\n\n- Tokens as custom properties in one file the base layout imports;\n  Tailwind v4 via `npx astro add tailwind` (tokens in `@theme`,\n  `ui-stack-tailwind`); an `is:inline` head script sets the theme before\n  the first paint (`ui-themes`).\n- Between pages, the browser's own transitions and no script:\n  `@view-transition { navigation: auto; }` inside `@media\n  (prefers-reduced-motion: no-preference)` (Chromium, Safari 18.2 and\n  later; others just load) (`motion-interface`).\n- `\u003CClientRouter \u002F>` (`astro:transitions`) when state must survive\n  navigation (`transition:persist`, `transition:name`); it turns its\n  animations off under reduced motion. Its trap: bundled scripts run once,\n  so page setup goes in `document.addEventListener(\"astro:page-load\",\n  setup)` or in custom elements.\n\n## 8. Head, docs, other frameworks\n\n- A head component: `\u003Ctitle>`, description, canonical\n  (`new URL(Astro.url.pathname, Astro.site)`), Open Graph; `site` in the\n  config (canonical, `@astrojs\u002Fsitemap` and `@astrojs\u002Frss` need it); `lang`\n  on `\u003Chtml>`; `i18n` routing (`frontend-seo`, `i18n`); `prefetch: true`;\n  `security: { csp: true }` for a Content Security Policy.\n- Docs: `npm create astro@latest -- --template starlight` (sidebar, Pagefind\n  search, both themes, code blocks with copy, `:::note` asides, `\u003CTabs\n  syncKey=\"pkg\">`, edit links; `ui-page-docs`), themed through\n  `--sl-color-accent`, `--sl-font` and component overrides.\n- `npx astro add react` (or `vue`, `svelte`): one framework, following its\n  skill (`ui-stack-react`, `ui-stack-vue`, `ui-stack-svelte`).\n\n## Check it\n\n- `npx astro check` (it offers to install `@astrojs\u002Fcheck` and\n  `typescript`): types in `.astro` files, props, collections.\n- `npx astro build` fails on a schema mismatch, a broken import, a missing\n  `alt` or an unclosed tag; in `dist\u002F`, display-only pages load no script.\n- The `preview` tool: `url` `http:\u002F\u002Flocalhost:4321\u002F`, `start` `npm run dev\n  -- --port 4321`. With `\u003CClientRouter \u002F>`, navigate three pages and use\n  each interactive part again. Then `ui_check`.\n\n## Avoid\n\n`client:load` on every component; a whole React app in one island for a\ncontent page; `client:only` for content that belongs in the HTML; content\nin hand-typed arrays; `Astro.glob`; content images in `public\u002F`;\n`set:html` with anything unsanitised; secrets under `PUBLIC_`; `output:\n\"server\"` for a site that could be static; scripts that die after a\nclient-side navigation; a font stylesheet from a CDN blocking first paint.\n",{"name":5,"description":7},"main","38293fe1b096e6bf3646c7344c38786ed688f464","crates\u002Fenowx-core\u002Fskills\u002Fui-stack-astro",21,"https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli","https:\u002F\u002Fenowx.ai","https:\u002F\u002Fgithub.com\u002Fenowdev","Pretending to be a Developer","2026-10-06 02:05:35",{"source":8,"skillName":5,"commands":59,"prompt":69,"zipAvailable":70},[60,63,66],{"tool":61,"command":62},"npx","npx skills add enowdev\u002Fenowxcli --skill ui-stack-astro",{"tool":64,"command":65},"bunx","bunx skills add enowdev\u002Fenowxcli --skill ui-stack-astro",{"tool":67,"command":68},"pnpm","pnpm dlx skills add enowdev\u002Fenowxcli --skill ui-stack-astro","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-astro\u002FSKILL.md",true,[],[73,86,111,125,152,174],{"skillNo":74,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":77,"categories":78,"keywords":82,"publishedAt":44},"01M6D2HQYSQD9N7MTNDT4A7CBM","demo-skill","Project-scope demo skill for tests.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fworkspace\u002F.agents\u002Fskills\u002Fdemo-skill\u002FSKILL.md",[79],{"categoryNo":80,"categoryName":81,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[83,84,85,31,28],"demo skill","demo","tests",{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":100,"publishedAt":44},"01M6D2HM0GCPM0TM2HPG9HHR2R","writing","Writing words people read: the specific thing instead of claims, words to drop, sentences, interface copy (buttons, labels, errors, empty states, confirmations), page copy, notifications and emails, alt text and link text, numbers and dates, words that translate well, documentation and voice. Read before writing a page's copy, an interface's text or a document.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fwriting\u002FSKILL.md",[92,95],{"categoryNo":93,"categoryName":94,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":96,"categoryName":97,"taxonomyType":98,"taxonomyTypeDesc":99},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[88,101,102,103,104,105,28,106,107,108,109,110],"text","emails","empty","drop","document","page","voice","notifications","documentation","translate",{"skillNo":112,"name":25,"nameZh":6,"description":113,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":114,"categories":115,"keywords":119,"publishedAt":44},"01M6D2HG07YS49NM196Q0R3AZ8","Designing or restyling an interface: direction, layout, type, colour, icons, components, states, responsive behaviour. Read before building a page, a screen or a set of components.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui\u002FSKILL.md",[116],{"categoryNo":117,"categoryName":118,"taxonomyType":98,"taxonomyTypeDesc":99},"01JBSEEDCAT000000000000032","设计师",[25,120,43,121,122,106,123,28,124],"icons","SET","responsive","layout","before building",{"skillNo":126,"name":127,"nameZh":6,"description":128,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":129,"categories":130,"keywords":137,"publishedAt":44},"01M6D2HB59HZHWG1RYZDV4SJ91","ui-themes","Light and dark themes, and colour that holds in both: tokens with a value per theme, and what each kind of content needs to stay visible (icons, SVG drawings and line art, logos, photos, screenshots, charts, code, maps, borders, shadows, focus, form controls), switching without a flash, and checking both. Read before building a second theme or a theme toggle, and before any drawing, logo or chart on a page that has two themes.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-themes\u002FSKILL.md",[131,134],{"categoryNo":132,"categoryName":133,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":135,"categoryName":136,"taxonomyType":98,"taxonomyTypeDesc":99},"01JBSEEDCAT000000000000030","开发工程师",[25,138,139,140,141,142,143,144,145,28,124,146,106,147,148,149,120,150,151],"ui themes","switching","photos","tokens","drawing","focus","maps","Art","flash","code","Charts","content","screenshots","logo",{"skillNo":153,"name":154,"nameZh":6,"description":155,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":156,"categories":157,"keywords":161,"publishedAt":44},"01M6D2H5R5KRAE524C8QX0T7Q0","ui-structure","Structure before styling: the blueprint written before any interface code (the screen's job, the regions its kind of page needs, their order and why, the layout skeleton in numbers, the components and their states), the anatomy and order of a homepage, a dashboard, a list, a detail page, settings, a form, sign-in, pricing, docs, an article and a checkout, and how to turn the blueprint into code (tokens, layout primitives, components, sections, page). Read first, before building or rebuilding any page or screen.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-structure\u002FSKILL.md",[158,159,160],{"categoryNo":93,"categoryName":94,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":135,"categoryName":136,"taxonomyType":98,"taxonomyTypeDesc":99},{"categoryNo":96,"categoryName":97,"taxonomyType":98,"taxonomyTypeDesc":99},[162,25,163,43,164,165,147,166,167,168,123,141,169,170,171,28,124,106,172,173],"structure","ui structure","pricing","dashboard","Blueprint","article","anatomy","styling","primitives","list","why","how",{"skillNo":175,"name":176,"nameZh":6,"description":177,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":178,"categories":179,"keywords":185,"publishedAt":44},"01M6D2H0ZZBJG77MBGPZHA6MWD","ui-stack-vue","Building interfaces in Vue 3 and Nuxt: project layout, script setup with typed defineProps, defineEmits and defineModel, attribute fallthrough, slots and scoped slots, template refs, ref, reactive and computed, watch versus watchEffect, composables and VueUse, provide and inject, Pinia, keyed v-for, transitions, Nuxt 4 (app directory, pages, layouts, useFetch and useAsyncData keys, $fetch in handlers, server routes, runtime config, useSeoMeta, NuxtImg, middleware), VeeValidate forms, Reka UI and Nuxt UI, testing, vue-tsc, and the traps. Read when the project uses Vue or Nuxt.","https:\u002F\u002Fgithub.com\u002Fenowdev\u002Fenowxcli\u002Fblob\u002Fmain\u002Fcrates\u002Fenowx-core\u002Fskills\u002Fui-stack-vue\u002FSKILL.md",[180,183,184],{"categoryNo":181,"categoryName":182,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":117,"categoryName":118,"taxonomyType":98,"taxonomyTypeDesc":99},[25,41,27,186,187,188,189,190,123,31,32,191,28,192,193,194,40,195,196,197],"ui stack vue","forms","pages","watch","runtime","fetch","pinia","nuxt","template","config","directory","testing"]