From 1702f5530593e6334ffdb0ac347ed6f6f6ae7d67 Mon Sep 17 00:00:00 2001 From: niapya Date: Sat, 4 Jul 2026 15:36:26 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E2=9C=A8=20move=20to=20a=20monorepo,=20u?= =?UTF-8?q?pdate=20dependency.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/create-feed/SKILL.md | 164 +- .agents/skills/datastar/SKILL.md | 226 + .agents/skills/datastar/patterns/howtos.md | 340 + .agents/skills/datastar/patterns/tao.md | 203 + .../skills/datastar/reference/attributes.md | 312 + .agents/skills/datastar/reference/sse.md | 233 + .agents/skills/kita-html/SKILL.md | 402 + .agents/skills/source-handler/SKILL.md | 134 +- .agents/skills/utils-reference/SKILL.md | 262 +- .gitignore | 8 +- .vscode/settings.json | 1 + CONTRIBUTING.EN.md | 40 + README.EN.md | 341 + README.md | 158 +- biome.jsonc | 23 +- bun.lock | 3530 ++++++- deno.json | 5 - deno.lock | 35 - package.json | 67 +- pkgs/rssbook/.gitignore | 5 + pkgs/rssbook/package.json | 39 + {src => pkgs/rssbook/src}/RSSBookApp.ts | 0 {src => pkgs/rssbook/src}/books/index.ts | 0 .../theme/error/components/ErrorDetails.tsx | 0 .../theme/error/components/ErrorFooter.tsx | 0 .../theme/error/components/ErrorHeader.tsx | 0 .../books/theme/error/components/InfoCard.tsx | 0 .../theme/error/components/InfoSection.tsx | 0 .../books/theme/error/components/PageHead.tsx | 0 .../books/theme/error/components/index.ts | 0 .../rssbook/src}/books/theme/error/index.tsx | 0 .../theme/home/components/Background.tsx | 4 +- .../books/theme/home/components/FilterBar.tsx | 0 .../books/theme/home/components/Footer.tsx | 0 .../src}/books/theme/home/components/Item.tsx | 6 +- .../src}/books/theme/home/components/Nav.tsx | 2 +- .../books/theme/home/components/Status.tsx | 0 .../books/theme/home/components/Timeline.tsx | 4 +- .../src}/books/theme/home/components/index.ts | 0 .../rssbook/src}/books/theme/home/index.tsx | 0 .../rssbook/src}/books/theme/index.ts | 2 +- src/node.ts => pkgs/rssbook/src/index.ts | 13 +- .../rssbook/src}/plugins/errorHandler.ts | 0 {src => pkgs/rssbook/src}/plugins/index.ts | 0 {src => pkgs/rssbook/src}/plugins/init.ts | 0 {src => pkgs/rssbook/src}/plugins/inject.ts | 0 {src => pkgs/rssbook/src}/plugins/logger.ts | 0 {src => pkgs/rssbook/src}/plugins/openAPI.ts | 0 {src => pkgs/rssbook/src}/plugins/render.ts | 0 {src => pkgs/rssbook/src}/plugins/static.ts | 4 +- {src => pkgs/rssbook/src}/public/favicon.ico | Bin {src => pkgs/rssbook/src}/public/favicon.svg | 8 +- {src => pkgs/rssbook/src}/public/robots.txt | 0 {src => pkgs/rssbook/src}/public/xsl/atom.xsl | 0 {src => pkgs/rssbook/src}/public/xsl/rss.xsl | 0 .../feeds/_example/_api/index.start.ts | 0 .../src}/routers/feeds/_example/_api/index.ts | 85 +- .../feeds/_example/_html/index.start.ts | 0 .../routers/feeds/_example/_html/index.ts | 130 +- .../src/routers/feeds/_example/index.ts | 42 + .../rssbook/src}/routers/feeds/acg/index.ts | 0 .../rssbook/src}/routers/feeds/bbs/index.ts | 0 .../rssbook/src}/routers/feeds/blog/index.ts | 0 .../src}/routers/feeds/design/index.ts | 0 .../src}/routers/feeds/finance/index.ts | 0 .../src}/routers/feeds/gaming/index.ts | 0 .../src}/routers/feeds/government/index.ts | 0 .../rssbook/src}/routers/feeds/index.ts | 0 .../rssbook/src}/routers/feeds/jobs/index.ts | 0 .../rssbook/src}/routers/feeds/live/index.ts | 0 .../src}/routers/feeds/multimedia/index.ts | 0 .../rssbook/src}/routers/feeds/news/index.ts | 0 .../src}/routers/feeds/others/index.ts | 0 .../src}/routers/feeds/programming/index.ts | 0 .../src}/routers/feeds/reading/index.ts | 0 .../src}/routers/feeds/research/index.ts | 0 .../src}/routers/feeds/school/index.ts | 0 .../src}/routers/feeds/shopping/index.ts | 0 .../src}/routers/feeds/socialmedia/index.ts | 0 .../src}/routers/feeds/travel/index.ts | 0 .../src}/routers/feeds/updates/index.ts | 0 {src => pkgs/rssbook/src}/routers/index.ts | 0 .../rssbook/src}/routers/utils/fetch.test.ts | 0 .../rssbook/src}/routers/utils/fetch.ts | 0 .../rssbook/src}/routers/utils/filter.test.ts | 0 .../rssbook/src}/routers/utils/filter.ts | 0 .../rssbook/src}/routers/utils/index.ts | 0 .../src}/routers/utils/intersection.test.ts | 0 .../src}/routers/utils/intersection.ts | 0 .../rssbook/src}/routers/utils/sort.test.ts | 0 .../rssbook/src}/routers/utils/sort.ts | 0 .../src}/routers/utils/transform.test.ts | 0 .../rssbook/src}/routers/utils/transform.ts | 0 .../rssbook/src}/routers/utils/union.test.ts | 0 .../rssbook/src}/routers/utils/union.ts | 0 .../rssbook/src}/tests/types/utils.test.ts | 0 .../rssbook/src}/tests/utils/cache.test.ts | 0 .../rssbook/src}/tests/utils/category.test.ts | 0 .../rssbook/src}/tests/utils/date.test.ts | 0 .../src}/tests/utils/detectLanguage.test.ts | 0 .../src}/tests/utils/feeds/filter.test.ts | 0 .../tests/utils/feeds/intersection.test.ts | 0 .../src}/tests/utils/feeds/parse.test.ts | 0 .../src}/tests/utils/feeds/render.test.ts | 0 .../src}/tests/utils/feeds/sort.test.ts | 0 .../src}/tests/utils/feeds/union.test.ts | 0 .../src}/tests/utils/formatHTML.test.ts | 0 .../rssbook/src}/tests/utils/load.test.ts | 0 .../rssbook/src}/tests/utils/logger.test.ts | 0 .../rssbook/src}/tests/utils/ofetch.test.ts | 0 .../rssbook/src}/tests/utils/route.test.ts | 0 .../rssbook/src}/tests/utils/sleep.test.ts | 0 .../rssbook/src}/tests/utils/source.test.ts | 0 .../src}/tests/utils/toAbsoluteURL.test.ts | 0 .../rssbook/src}/tests/utils/uuid.test.ts | 0 {src => pkgs/rssbook/src}/types/data.ts | 0 {src => pkgs/rssbook/src}/types/index.ts | 0 {src => pkgs/rssbook/src}/types/meta.ts | 0 {src => pkgs/rssbook/src}/types/route.ts | 0 {src => pkgs/rssbook/src}/types/source.ts | 0 {src => pkgs/rssbook/src}/types/theme.ts | 0 {src => pkgs/rssbook/src}/types/utils.ts | 0 {src => pkgs/rssbook/src}/utils/cache.ts | 4 +- {src => pkgs/rssbook/src}/utils/category.ts | 0 {src => pkgs/rssbook/src}/utils/date.ts | 0 .../rssbook/src}/utils/detectLanguage.ts | 0 .../rssbook/src}/utils/dispatchRoute.ts | 0 .../rssbook/src}/utils/feeds/filter.ts | 2 +- .../rssbook/src}/utils/feeds/index.ts | 0 .../rssbook/src}/utils/feeds/intersection.ts | 0 .../rssbook/src}/utils/feeds/parse.ts | 0 .../rssbook/src}/utils/feeds/render.ts | 2 +- {src => pkgs/rssbook/src}/utils/feeds/sort.ts | 0 .../rssbook/src}/utils/feeds/union.ts | 0 {src => pkgs/rssbook/src}/utils/formatHTML.ts | 0 {src => pkgs/rssbook/src}/utils/index.ts | 0 {src => pkgs/rssbook/src}/utils/load.ts | 1 + {src => pkgs/rssbook/src}/utils/logger.ts | 2 +- {src => pkgs/rssbook/src}/utils/ofetch.ts | 0 {src => pkgs/rssbook/src}/utils/sleep.ts | 0 {src => pkgs/rssbook/src}/utils/source.ts | 0 {src => pkgs/rssbook/src}/utils/t.ts | 0 .../rssbook/src}/utils/toAbsoluteURL.ts | 0 {src => pkgs/rssbook/src}/utils/uuid.ts | 0 pkgs/rssbook/tsconfig.json | 11 + pkgs/utils-feed-helper/.gitignore | 52 + pkgs/utils-feed-helper/bun.lock | 56 + pkgs/utils-feed-helper/package.json | 25 + pkgs/utils-feed-helper/src/index.tsx | 43 + pkgs/utils-feed-helper/tsconfig.json | 22 + platform/cloudflare/.gitignore | 5 + .../cloudflare/index.ts | 16 +- platform/cloudflare/package.json | 16 + platform/cloudflare/tsconfig.json | 18 + platform/cloudflare/worker-configuration.d.ts | 8338 +++++++++++++++++ platform/cloudflare/wrangler.jsonc | 33 + platform/deno/.gitignore | 4 + platform/deno/deno.json | 7 + platform/deno/index.ts | 38 + platform/deno/package.json | 13 + platform/deno/tsconfig.json | 11 + platform/netlify/.gitignore | 7 + .../netlify}/index.ts | 4 +- netlify.toml => platform/netlify/netlify.toml | 3 +- platform/netlify/package.json | 16 + platform/netlify/tsconfig.json | 11 + platform/node/.gitignore | 5 + platform/node/index.ts | 27 + platform/node/package.json | 17 + platform/node/tsconfig.json | 11 + platform/vercel/.gitignore | 6 + src/deno.ts => platform/vercel/index.ts | 10 +- platform/vercel/package.json | 16 + platform/vercel/tsconfig.json | 11 + platform/vercel/vercel.json | 11 + scripts/add.source.ts | 22 +- scripts/test.source.ts | 9 +- src/index.ts | 18 - src/routers/feeds/_example/index.ts | 27 - tsconfig.base.json | 22 + tsconfig.json | 32 +- vercel.json | 5 - worker-configuration.d.ts | 7421 --------------- wrangler.jsonc | 33 - 184 files changed, 15079 insertions(+), 8212 deletions(-) create mode 100644 .agents/skills/datastar/SKILL.md create mode 100644 .agents/skills/datastar/patterns/howtos.md create mode 100644 .agents/skills/datastar/patterns/tao.md create mode 100644 .agents/skills/datastar/reference/attributes.md create mode 100644 .agents/skills/datastar/reference/sse.md create mode 100644 .agents/skills/kita-html/SKILL.md delete mode 100644 deno.json delete mode 100644 deno.lock create mode 100644 pkgs/rssbook/.gitignore create mode 100644 pkgs/rssbook/package.json rename {src => pkgs/rssbook/src}/RSSBookApp.ts (100%) rename {src => pkgs/rssbook/src}/books/index.ts (100%) rename {src => pkgs/rssbook/src}/books/theme/error/components/ErrorDetails.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/error/components/ErrorFooter.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/error/components/ErrorHeader.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/error/components/InfoCard.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/error/components/InfoSection.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/error/components/PageHead.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/error/components/index.ts (100%) rename {src => pkgs/rssbook/src}/books/theme/error/index.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/home/components/Background.tsx (96%) rename {src => pkgs/rssbook/src}/books/theme/home/components/FilterBar.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/home/components/Footer.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/home/components/Item.tsx (94%) rename {src => pkgs/rssbook/src}/books/theme/home/components/Nav.tsx (98%) rename {src => pkgs/rssbook/src}/books/theme/home/components/Status.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/home/components/Timeline.tsx (92%) rename {src => pkgs/rssbook/src}/books/theme/home/components/index.ts (100%) rename {src => pkgs/rssbook/src}/books/theme/home/index.tsx (100%) rename {src => pkgs/rssbook/src}/books/theme/index.ts (90%) rename src/node.ts => pkgs/rssbook/src/index.ts (52%) rename {src => pkgs/rssbook/src}/plugins/errorHandler.ts (100%) rename {src => pkgs/rssbook/src}/plugins/index.ts (100%) rename {src => pkgs/rssbook/src}/plugins/init.ts (100%) rename {src => pkgs/rssbook/src}/plugins/inject.ts (100%) rename {src => pkgs/rssbook/src}/plugins/logger.ts (100%) rename {src => pkgs/rssbook/src}/plugins/openAPI.ts (100%) rename {src => pkgs/rssbook/src}/plugins/render.ts (100%) rename {src => pkgs/rssbook/src}/plugins/static.ts (78%) rename {src => pkgs/rssbook/src}/public/favicon.ico (100%) rename {src => pkgs/rssbook/src}/public/favicon.svg (73%) rename {src => pkgs/rssbook/src}/public/robots.txt (100%) rename {src => pkgs/rssbook/src}/public/xsl/atom.xsl (100%) rename {src => pkgs/rssbook/src}/public/xsl/rss.xsl (100%) rename {src => pkgs/rssbook/src}/routers/feeds/_example/_api/index.start.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/_example/_api/index.ts (72%) rename {src => pkgs/rssbook/src}/routers/feeds/_example/_html/index.start.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/_example/_html/index.ts (57%) create mode 100644 pkgs/rssbook/src/routers/feeds/_example/index.ts rename {src => pkgs/rssbook/src}/routers/feeds/acg/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/bbs/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/blog/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/design/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/finance/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/gaming/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/government/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/jobs/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/live/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/multimedia/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/news/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/others/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/programming/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/reading/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/research/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/school/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/shopping/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/socialmedia/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/travel/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/feeds/updates/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/fetch.test.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/fetch.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/filter.test.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/filter.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/index.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/intersection.test.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/intersection.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/sort.test.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/sort.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/transform.test.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/transform.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/union.test.ts (100%) rename {src => pkgs/rssbook/src}/routers/utils/union.ts (100%) rename {src => pkgs/rssbook/src}/tests/types/utils.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/cache.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/category.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/date.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/detectLanguage.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/feeds/filter.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/feeds/intersection.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/feeds/parse.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/feeds/render.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/feeds/sort.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/feeds/union.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/formatHTML.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/load.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/logger.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/ofetch.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/route.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/sleep.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/source.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/toAbsoluteURL.test.ts (100%) rename {src => pkgs/rssbook/src}/tests/utils/uuid.test.ts (100%) rename {src => pkgs/rssbook/src}/types/data.ts (100%) rename {src => pkgs/rssbook/src}/types/index.ts (100%) rename {src => pkgs/rssbook/src}/types/meta.ts (100%) rename {src => pkgs/rssbook/src}/types/route.ts (100%) rename {src => pkgs/rssbook/src}/types/source.ts (100%) rename {src => pkgs/rssbook/src}/types/theme.ts (100%) rename {src => pkgs/rssbook/src}/types/utils.ts (100%) rename {src => pkgs/rssbook/src}/utils/cache.ts (97%) rename {src => pkgs/rssbook/src}/utils/category.ts (100%) rename {src => pkgs/rssbook/src}/utils/date.ts (100%) rename {src => pkgs/rssbook/src}/utils/detectLanguage.ts (100%) rename {src => pkgs/rssbook/src}/utils/dispatchRoute.ts (100%) rename {src => pkgs/rssbook/src}/utils/feeds/filter.ts (99%) rename {src => pkgs/rssbook/src}/utils/feeds/index.ts (100%) rename {src => pkgs/rssbook/src}/utils/feeds/intersection.ts (100%) rename {src => pkgs/rssbook/src}/utils/feeds/parse.ts (100%) rename {src => pkgs/rssbook/src}/utils/feeds/render.ts (96%) rename {src => pkgs/rssbook/src}/utils/feeds/sort.ts (100%) rename {src => pkgs/rssbook/src}/utils/feeds/union.ts (100%) rename {src => pkgs/rssbook/src}/utils/formatHTML.ts (100%) rename {src => pkgs/rssbook/src}/utils/index.ts (100%) rename {src => pkgs/rssbook/src}/utils/load.ts (98%) rename {src => pkgs/rssbook/src}/utils/logger.ts (98%) rename {src => pkgs/rssbook/src}/utils/ofetch.ts (100%) rename {src => pkgs/rssbook/src}/utils/sleep.ts (100%) rename {src => pkgs/rssbook/src}/utils/source.ts (100%) rename {src => pkgs/rssbook/src}/utils/t.ts (100%) rename {src => pkgs/rssbook/src}/utils/toAbsoluteURL.ts (100%) rename {src => pkgs/rssbook/src}/utils/uuid.ts (100%) create mode 100644 pkgs/rssbook/tsconfig.json create mode 100644 pkgs/utils-feed-helper/.gitignore create mode 100644 pkgs/utils-feed-helper/bun.lock create mode 100644 pkgs/utils-feed-helper/package.json create mode 100644 pkgs/utils-feed-helper/src/index.tsx create mode 100644 pkgs/utils-feed-helper/tsconfig.json create mode 100644 platform/cloudflare/.gitignore rename src/cloudflare.ts => platform/cloudflare/index.ts (55%) create mode 100644 platform/cloudflare/package.json create mode 100644 platform/cloudflare/tsconfig.json create mode 100644 platform/cloudflare/worker-configuration.d.ts create mode 100644 platform/cloudflare/wrangler.jsonc create mode 100644 platform/deno/.gitignore create mode 100644 platform/deno/deno.json create mode 100644 platform/deno/index.ts create mode 100644 platform/deno/package.json create mode 100644 platform/deno/tsconfig.json create mode 100644 platform/netlify/.gitignore rename {netlify/edge-functions => platform/netlify}/index.ts (77%) rename netlify.toml => platform/netlify/netlify.toml (61%) create mode 100644 platform/netlify/package.json create mode 100644 platform/netlify/tsconfig.json create mode 100644 platform/node/.gitignore create mode 100644 platform/node/index.ts create mode 100644 platform/node/package.json create mode 100644 platform/node/tsconfig.json create mode 100644 platform/vercel/.gitignore rename src/deno.ts => platform/vercel/index.ts (55%) create mode 100644 platform/vercel/package.json create mode 100644 platform/vercel/tsconfig.json create mode 100644 platform/vercel/vercel.json delete mode 100644 src/index.ts delete mode 100644 src/routers/feeds/_example/index.ts create mode 100644 tsconfig.base.json delete mode 100644 vercel.json delete mode 100644 worker-configuration.d.ts delete mode 100644 wrangler.jsonc diff --git a/.agents/skills/create-feed/SKILL.md b/.agents/skills/create-feed/SKILL.md index 89fea0c..3a70d71 100644 --- a/.agents/skills/create-feed/SKILL.md +++ b/.agents/skills/create-feed/SKILL.md @@ -1,150 +1,150 @@ --- name: create-feed -description: 创建新的 RSS Feed 源。当用户想要添加新的数据源、创建 Feed 路由、或询问如何抓取网站数据时使用此技能。 +description: Create new RSS Feed sources. Use this skill when users want to add new data sources, create Feed routes, or ask how to scrape website data. --- -# 创建新 Feed 指南 +# Creating a New Feed Guide -本技能帮助你在 RSSBook 项目中创建新的 RSS Feed 源。 +This skill helps you create new RSS Feed sources in the RSSBook project. -## 核心概念 +## Core Concepts -### 1. Source(数据源) +### 1. Source (Data Source) -Source 是一个数据源的定义,代表一个网站或服务(如 GitHub、Twitter)。每个 Source 可以包含多个 Feed。 +A Source is a data source definition, representing a website or service (e.g., GitHub, Twitter). Each Source can contain multiple Feeds. -**文件位置**: `src/routers/feeds/{category}/{slug}/index.ts` +**File Location**: `src/routers/feeds/{category}/{slug}/index.ts` -### 2. Feed(订阅源) +### 2. Feed -Feed 是 Source 下的具体路由,定义了如何抓取和返回数据。 +A Feed is a specific route under a Source, defining how to fetch and return data. -### 3. Category(分类) +### 3. Category -Category 将多个 Source 组织在一起,如 `programming`、`news`、`blog` 等。 +A Category organizes multiple Sources, such as `programming`, `news`, `blog`, etc. -## 创建步骤 +## Creation Steps -### 步骤 1:确定分类和 slug +### Step 1: Determine Category and Slug -- **分类**: 选择现有分类或创建新分类 -- **slug**: 小写字母和短横线,如 `my-source` +- **Category**: Choose an existing category or create a new one +- **Slug**: Lowercase letters and hyphens, e.g., `my-source` -### 步骤 2:创建 Source 文件 +### Step 2: Create Source File -在 `src/routers/feeds/{category}/{slug}/index.ts` 创建文件: +Create a file at `src/routers/feeds/{category}/{slug}/index.ts`: ```typescript import type { Data, DataItem } from "@/types"; import { Source, t } from "@/utils"; export default new Source({ - slug: "example-source", // 必须与文件夹名一致 + slug: "example-source", // Must match the folder name title: "Example Source", - description: "简短描述这个数据源", - domain: "example.com", // 源网站的域名 + description: "Brief description of this data source", + domain: "example.com", // Domain of the source website config: { - // 可选:需要的配置参数 + // Optional: required configuration parameters API_KEY: { - description: "API 密钥", + description: "API Key", required: true, default: "your-default-key", }, }, }).feed( { - title: "Feed 标题", - description: "Feed 的详细描述(支持 Markdown)", + title: "Feed Title", + description: "Detailed description of the Feed (supports Markdown)", fulltext: true, language: ["zh-CN", "en"], maintainer: { name: "Your Name" }, withImage: "If-Present", }, (app) => app.get("/path/:param", async (context) => { - // 抓取逻辑 + // Fetching logic return data satisfies Data; }), ); ``` -### 步骤 3:注册到 Category +### Step 3: Register to Category -在 `src/routers/feeds/{category}/index.ts` 中注册: +Register in `src/routers/feeds/{category}/index.ts`: ```typescript import { Category } from "@/utils"; import mySource from "./my-source"; -export default new Category("category-name", "分类描述").use({ +export default new Category("category-name", "Category description").use({ mySource, }); ``` -## Handler 上下文 +## Handler Context -Handler 函数接收的上下文对象包含: +The context object received by the handler function contains: -### Props(属性) +### Props -| 属性 | 说明 | -|------|------| -| `meta` | 源和 Feed 的元数据(domain、config、title 等) | -| `params` | 路由参数(如 `:username`) | -| `query` | 查询参数 | -| `lang` | 请求语言(从 Accept-Language 解析) | +| Property | Description | +|----------|-------------| +| `meta` | Metadata for the source and Feed (domain, config, title, etc.) | +| `params` | Route parameters (e.g., `:username`) | +| `query` | Query parameters | +| `lang` | Request language (parsed from Accept-Language) | -### Functions(函数) +### Functions -| 函数 | 说明 | -|------|------| -| `cache` | 缓存对象,使用 `cache.tryGet(key, fn)` | -| `date` | 日期解析函数 | -| `ofetch` | 增强的 fetch 函数 | -| `load` | HTML 解析器(类似 jQuery) | -| `formatHTML` | HTML 清理和格式化 | -| `toAbsoluteURL` | 相对 URL 转绝对 URL | -| `parse` | 解析 RSS/Atom Feed | -| `logger` | 日志工具 | +| Function | Description | +|----------|-------------| +| `cache` | Cache object, use `cache.tryGet(key, fn)` | +| `date` | Date parsing function | +| `ofetch` | Enhanced fetch function | +| `load` | HTML parser (jQuery-like) | +| `formatHTML` | HTML cleanup and formatting | +| `toAbsoluteURL` | Relative URL to absolute URL | +| `parse` | Parse RSS/Atom Feed | +| `logger` | Logging tool | -## Data 类型结构 +## Data Type Structure -返回的数据必须符合 `Data` 类型: +The returned data must conform to the `Data` type: ```typescript interface Data { - title: string; // Feed 标题 - link: string; // Feed 链接 - description?: string; // Feed 描述 - language?: string; // 语言代码 - item?: DataItem[]; // Feed 条目 - updated?: Date; // 更新时间 + title: string; // Feed title + link: string; // Feed link + description?: string; // Feed description + language?: string; // Language code + item?: DataItem[]; // Feed items + updated?: Date; // Update time } interface DataItem { - title: string; // 条目标题 - link: string; // 条目链接 - description?: string; // 摘要 - content?: string; // 全文内容 - date?: Date; // 发布日期 - author?: Author[]; // 作者 - category?: Category[]; // 分类 - image?: string; // 图片 - id?: string; // 唯一标识 + title: string; // Item title + link: string; // Item link + description?: string; // Summary + content?: string; // Full text content + date?: Date; // Publication date + author?: Author[]; // Authors + category?: Category[]; // Categories + image?: string; // Image + id?: string; // Unique identifier } ``` -## 示例:API 数据源 +## Example: API Data Source ```typescript export default new Source({ slug: "github", title: "GitHub", - description: "GitHub 代码托管平台", + description: "GitHub code hosting platform", domain: "github.com", }).feed( { - title: "用户事件", - description: "获取 GitHub 用户的活动事件", + title: "User Events", + description: "Fetch GitHub user activity events", language: ["en"], maintainer: { name: "RSSBook" }, }, @@ -171,25 +171,25 @@ export default new Source({ }, { params: t.Object({ - username: t.String({ description: "GitHub 用户名" }), + username: t.String({ description: "GitHub username" }), }), }, ), ); ``` -## 示例:HTML 网页抓取 +## Example: HTML Web Scraping ```typescript export default new Source({ slug: "blog", title: "Blog", - description: "博客网站", + description: "Blog website", domain: "blog.example.com", }).feed( { - title: "最新文章", - description: "获取博客最新文章", + title: "Latest Articles", + description: "Fetch latest blog articles", fulltext: true, language: ["zh-CN"], maintainer: { name: "RSSBook" }, @@ -215,7 +215,7 @@ export default new Source({ }); return { - title: "Blog 最新文章", + title: "Blog Latest Articles", link: rootURL, item: items, } satisfies Data; @@ -224,11 +224,11 @@ export default new Source({ ); ``` -## 最佳实践 +## Best Practices -1. **使用缓存**: 始终使用 `cache.tryGet()` 避免重复请求 -2. **类型安全**: 使用 `satisfies Data` 和 `satisfies DataItem` 确保类型正确 -3. **错误处理**: 在抓取全文时使用 try-catch -4. **并发请求**: 使用 `Promise.all()` 并发获取多个页面 -5. **URL 处理**: 使用 `toAbsoluteURL()` 处理相对链接 -6. **HTML 清理**: 使用 `formatHTML()` 清理 HTML 内容 +1. **Use Cache**: Always use `cache.tryGet()` to avoid repeated requests +2. **Type Safety**: Use `satisfies Data` and `satisfies DataItem` to ensure type correctness +3. **Error Handling**: Use try-catch when fetching full text content +4. **Concurrent Requests**: Use `Promise.all()` to fetch multiple pages concurrently +5. **URL Handling**: Use `toAbsoluteURL()` for relative links +6. **HTML Cleanup**: Use `formatHTML()` to clean HTML content diff --git a/.agents/skills/datastar/SKILL.md b/.agents/skills/datastar/SKILL.md new file mode 100644 index 0000000..aa410a8 --- /dev/null +++ b/.agents/skills/datastar/SKILL.md @@ -0,0 +1,226 @@ +--- +name: datastar +description: Hypermedia framework for building reactive web applications with backend-driven UI. Use this skill for Datastar development patterns, SSE streaming, signal management, DOM morphing, and the "Datastar way" philosophy. Covers data-* attributes, backend integration, and real-time collaborative app patterns. +version: 1.0.0 +--- + +# Datastar + +Datastar is a lightweight (~11KB) hypermedia framework for building reactive web applications. It enables server-side rendering with frontend framework capabilities by accepting HTML and SSE responses. The backend drives the frontend - this is the core philosophy. + +## The Tao of Datastar (Philosophy) + +The "Datastar way" is a set of opinions from the core team on building maintainable, scalable, high-performance web apps. + +### Core Principles + +1. **Backend is Source of Truth**: Most application state should reside on the server. The frontend is exposed to users and cannot be trusted. + +2. **Sensible Defaults**: Resist the urge to customize before questioning whether changes are truly necessary. + +3. **DOM Patching Over Fine-Grained Updates**: The backend drives frontend changes by patching HTML elements and signals. Send large DOM chunks ("fat morph") efficiently. + +4. **Morphing as Core Strategy**: Only modified parts of the DOM are updated, preserving state and improving performance. + +5. **Restrained Signal Usage**: Signals should serve specific purposes: + - User interactions (toggling visibility) + - Binding form inputs to backend requests + - Overusing signals indicates inappropriate frontend state management + +### Anti-Patterns to AVOID + +- **Optimistic Updates**: Don't update UI speculatively. Use loading indicators and confirm outcomes only after backend verification. +- **Assuming Fresh Frontend State**: Don't trust frontend cache. Fetch current state from backend. +- **Custom History Management**: Don't manually manipulate browser history. Use standard navigation. + +## Architecture: Event Streams All The Way Down + +Datastar leverages Server-Sent Events (SSE) as its foundation. The server pushes data through a persistent connection. + +**Key insight**: Datastar extends `text/event-stream` beyond SSE's GET-only limitation. HTML fragments wrapped in this protocol support POST, PUT, PATCH, DELETE operations. + +### Request/Response Flow + +1. User triggers action (click, form submission) +2. Frontend serializes ALL signals (except `_`-prefixed) and sends request +3. Backend reads signals and processes business logic +4. Server streams SSE events back to browser +5. Frontend receives events and updates DOM/signals reactively + +## SSE Event Types + +### `datastar-patch-elements` + +Modifies DOM elements through morphing. + +``` +event: datastar-patch-elements +data: elements
Hello world!
+``` + +**Options:** +- `selector` - CSS selector for target element +- `mode` - `outer` (default), `inner`, `replace`, `prepend`, `append`, `before`, `after`, `remove` +- `useViewTransition` - Enable view transitions + +### `datastar-patch-signals` + +Updates reactive state on the page. + +``` +event: datastar-patch-signals +data: signals {foo: 1, bar: 2} +``` + +Remove signals by setting to `null`. + +## Core Attributes + +### Event Handling +- `data-on:click` - Handle click events +- `data-on:submit` - Handle form submissions +- `data-on:keydown__window` - Global key events (with `__window` modifier) +- `data-on-intersect` - Visibility triggers +- `data-on-interval` - Periodic triggers + +### State Management +- `data-signals` - Define local signals: `data-signals="{count: 0}"` +- `data-bind` - Two-way binding: `data-bind:value="email"` +- `data-computed` - Derived values +- `data-init` - Initialize on load + +### DOM Updates +- `data-text` - Set text content: `data-text="$count"` +- `data-show` - Conditional visibility +- `data-class` - Dynamic classes +- `data-attr` - Dynamic attributes +- `data-ref` - Element references + +### Morphing Control +- `data-ignore` - Skip this element during morph +- `data-ignore-morph` - Preserve element across morphs +- `data-preserve-attr` - Keep specific attributes + +### Backend Actions +- `@get('/path')` - GET request +- `@post('/path')` - POST request +- `@put('/path')` - PUT request +- `@patch('/path')` - PATCH request +- `@delete('/path')` - DELETE request + +### Utility Actions +- `@peek()` - Access signals without subscribing +- `@setAll()` - Set all matching signals +- `@toggleAll()` - Toggle boolean signals + +## Signal Naming Convention + +- Regular signals: sent with every request +- `_`-prefixed signals: local only, NOT sent to backend + +```html +
+ +
+``` + +## Backend SDK Pattern + +All SDKs provide helpers for reading signals and streaming responses: + +```go +// Go example +func handler(w http.ResponseWriter, r *http.Request) { + signals := datastar.ReadSignals(r) + + sse := datastar.NewSSE(w) + sse.PatchElements("#result", "
Updated!
") + sse.PatchSignals(map[string]any{"count": signals.Count + 1}) +} +``` + +Available SDKs: Go, Python, TypeScript, PHP, Ruby, Rust, Java, Kotlin, Scala, C#, Clojure + +## Common Patterns + +### Loading Indicators + +```html + + +``` + +### Form Binding + +```html +
+ + + +
+``` + +### Conditional Rendering + +```html +
+ +
+ Details here... +
+
+``` + +### Polling + +```html +
+ +
+``` + +### Lazy Loading + +```html +
+ Loading... +
+``` + +## CQRS Pattern + +Segregate commands (writes) from queries (reads): +- **Long-lived read connections**: Real-time updates via SSE +- **Short-lived write requests**: POST/PUT/DELETE + +This enables real-time collaboration patterns. + +## Performance Tips + +1. **Use Compression**: Brotli on SSE streams can achieve 200:1 ratios due to repetitive DOM data +2. **Fat Morph**: Don't fear sending large HTML chunks - morphing is efficient +3. **Debounce Input**: Use `data-on:input.debounce_500ms` for search fields + +## When to Use This Skill + +- Building reactive web UIs without heavy JS frameworks +- Implementing real-time features (chat, notifications, live updates) +- Converting traditional server-rendered apps to be more interactive +- Questions about Datastar attributes, patterns, or philosophy +- Debugging SSE connections or signal behavior +- Choosing between client-side and server-side state + +## Skill Contents + +- `patterns/` - Common implementation patterns and anti-patterns +- `reference/` - Attribute and action reference documentation + +## Links + +- [Official Documentation](https://data-star.dev) +- [GitHub Repository](https://github.com/starfederation/datastar) +- [Discord Community](https://discord.gg/datastar) +- [Examples](https://data-star.dev/examples) diff --git a/.agents/skills/datastar/patterns/howtos.md b/.agents/skills/datastar/patterns/howtos.md new file mode 100644 index 0000000..8fd5583 --- /dev/null +++ b/.agents/skills/datastar/patterns/howtos.md @@ -0,0 +1,340 @@ +# Datastar How-To Patterns + +Common implementation patterns for Datastar applications. + +## Form Handling + +### Basic Form Submission + +```html +
+ + + + +
+
+``` + +Backend responds with: +``` +event: datastar-patch-elements +data: selector #login-result +data: elements
Welcome back!
+``` + +### Inline Validation + +```html +
+ + +
+``` + +### File Upload + +```html +
+ + + +
+``` + +## Lists and Tables + +### Click to Edit Row + +```html + + + + + +
Alice
+``` + +Backend replaces row with edit form: +``` +event: datastar-patch-elements +data: selector #user-1 +data: elements +data: elements +data: elements +data: elements +data: elements +data: elements +data: elements +``` + +### Delete Row with Confirmation + +```html + + Item Name + + + + +``` + +### Infinite Scroll / Load More + +```html +
+ +
+
+ Loading more... +
+``` + +Backend appends new items and updates trigger: +``` +event: datastar-patch-elements +data: mode append +data: selector #items +data: elements
New Item 1
+data: elements
New Item 2
+ +event: datastar-patch-elements +data: selector #load-more +data: elements
+data: elements Loading more... +data: elements
+``` + +## Real-Time Updates + +### Polling for Status + +```html +
+ Checking status... +
+``` + +### Live Notifications + +Keep a persistent SSE connection for push updates: + +```html +
+ +
+``` + +Backend sends events as they occur: +``` +event: datastar-patch-elements +data: mode prepend +data: selector #notifications +data: elements
  • New message from Alice
  • + +``` + +## Search and Filtering + +### Debounced Search + +```html +
    + +
    +
    +``` + +### Filter Controls + +```html +
    + + + + +
    +
    +``` + +## UI Interactions + +### Toggle Menu (Local State) + +```html +
    + + +
    +``` + +### Modal Dialog + +```html +
    + + + +
    +``` + +### Tabs + +```html +
    +
    + + + +
    + +
    + +
    +
    + +
    +
    + +
    +
    +``` + +## Keyboard Shortcuts + +### Global Key Bindings + +```html +
    + +
    +``` + +### Form Navigation + +```html +
    + + Ctrl+Enter to submit +
    +``` + +## Progress and Loading + +### Progress Bar + +```html +
    + + +
    +``` + +Backend streams progress updates: +``` +event: datastar-patch-signals +data: signals {progress: 25} + +event: datastar-patch-signals +data: signals {progress: 50} + +event: datastar-patch-signals +data: signals {progress: 75} + +event: datastar-patch-signals +data: signals {progress: 100} + +event: datastar-patch-elements +data: selector #processing +data: elements

    Complete!

    +``` + +## Preventing SSE Connection Timeouts + +For long-lived connections, send periodic keep-alive events: + +```go +// Backend sends empty comment every 30 seconds +ticker := time.NewTicker(30 * time.Second) +for { + select { + case <-ticker.C: + fmt.Fprintf(w, ": keepalive\n\n") + flusher.Flush() + case event := <-events: + // Send actual event + } +} +``` + +## Backend Redirect + +Redirect from the backend by patching a signal that triggers navigation: + +```html +
    +
    +``` + +Or use the standard approach - send a redirect header and let the browser handle it. diff --git a/.agents/skills/datastar/patterns/tao.md b/.agents/skills/datastar/patterns/tao.md new file mode 100644 index 0000000..91cd483 --- /dev/null +++ b/.agents/skills/datastar/patterns/tao.md @@ -0,0 +1,203 @@ +# The Tao of Datastar + +The "Datastar way" is a set of opinions from the core team on building maintainable, scalable, high-performance web apps. + +## Foundational Mindset + +**Backend-driven architecture**: Most application state should reside on the server, making it the authoritative source of truth. The frontend is exposed to users and cannot be trusted. + +## Core Tenets + +### 1. State Management Philosophy + +Keep business logic and state decisions on the backend rather than scattering them across frontend JavaScript. This reduces complexity and ensures consistency. + +**DO:** +```html + +
    + Toggle Feature +
    + +``` + +**DON'T:** +```html + +
    + +
    +``` + +### 2. Sensible Defaults + +The framework ships with recommended configuration settings. Resist the urge to customize before thoroughly questioning whether such changes are truly necessary. + +### 3. DOM Patching Over Fine-Grained Updates + +Rather than managing individual element updates, the backend drives frontend changes by patching HTML elements and signals. This "fat morph" approach sends large DOM chunks efficiently. + +**DO:** +```html + +event: datastar-patch-elements +data: elements
    +data: elements
    Alice
    +data: elements
    Bob
    +data: elements
    Charlie
    +data: elements
    +``` + +**DON'T:** +```javascript +// Don't try to surgically update individual items +users.forEach(u => updateUserElement(u.id, u.name)); +``` + +### 4. Morphing as Core Strategy + +Morphing ensures only modified parts of the DOM are updated, preserving state and improving performance. This enables sending substantial HTML trees from the backend while maintaining performance. + +### 5. Restrained Signal Usage + +Signals should serve specific purposes: +- **User interactions**: toggling visibility, local UI state +- **Form input binding**: connecting inputs to backend requests + +**Appropriate signal use:** +```html +
    + + +
    +``` + +**Overusing signals (anti-pattern):** +```html + +
    +``` + +## Key Patterns + +### Server-Sent Events (SSE) + +Use SSE responses to stream multiple events that patch elements, update signals, and execute scripts without requiring specialized content types. + +### Compression + +Leverage compression (particularly Brotli) on SSE streams. Compression ratios of 200:1 are realistic given the repetitive nature of DOM data. + +### CQRS (Command-Query Responsibility Segregation) + +Segregate commands (writes) from queries (reads): +- **Long-lived read connections**: Real-time updates +- **Short-lived write requests**: State changes + +This enables real-time collaboration patterns. + +### Backend Templating + +Use server-side templating to generate HTML and maintain DRY principles rather than duplicating logic across frontend and backend. + +### Page Navigation + +Embrace standard web patterns: +- Use anchor elements for navigation +- Use redirects from the backend +- Avoid custom history management +- Let browsers handle navigation naturally + +## Anti-Patterns to AVOID + +### 1. Optimistic Updates + +**WRONG:** +```html + +
    + Save +
    +``` + +**RIGHT:** +```html + +
    + Save +
    +Saving... +``` + +Rather than displaying success messages that might later fail, use loading indicators and confirm outcomes only after backend verification. + +### 2. Assume Stale Frontend State + +Don't assume frontend state remains current. Fetch current state from the backend rather than pre-loading and trusting frontend cache. + +**WRONG:** +```html + +
    + +
    +``` + +**RIGHT:** +```html + +
    + Loading... +
    +``` + +### 3. Custom History Management + +Adding complexity through manual browser history manipulation contradicts natural web navigation behavior. + +**WRONG:** +```html +
    +``` + +**RIGHT:** +```html +Go to page + +``` + +## User Experience Principles + +### Loading Indicators + +Use the `data-indicator` attribute to inform users when operations are in progress, providing transparency without deception. + +```html + + +``` + +### Accessibility + +Datastar remains neutral on accessibility. You must: +- Employ semantic HTML +- Apply ARIA appropriately +- Ensure keyboard navigation +- Support screen readers + +## Summary + +Build collaboratively with the browser and server, respecting their respective strengths while maintaining **backend authority over application truth**. diff --git a/.agents/skills/datastar/reference/attributes.md b/.agents/skills/datastar/reference/attributes.md new file mode 100644 index 0000000..7f68dd9 --- /dev/null +++ b/.agents/skills/datastar/reference/attributes.md @@ -0,0 +1,312 @@ +# Datastar Attributes Reference + +All Datastar functionality is accessed through `data-*` attributes on HTML elements. + +## Event Handling + +### data-on:[event] + +Handle DOM events with optional modifiers. + +```html + + + + + + + + + + +
    + + + +``` + +**Modifiers:** +- `.prevent` - preventDefault() +- `.stop` - stopPropagation() +- `.once` - Fire only once +- `.debounce_Xms` - Debounce by X milliseconds +- `.throttle_Xms` - Throttle by X milliseconds +- `__window` - Listen on window instead of element + +### data-on-intersect + +Trigger when element enters viewport. + +```html +
    + Loading... +
    + + +
    +``` + +### data-on-interval + +Trigger at regular intervals. + +```html + +
    + Status: Loading... +
    +``` + +### data-on-signal-patch + +Trigger when any signal changes. + +```html +
    +``` + +### data-on-signal-patch-filter + +Trigger when specific signals change. + +```html +
    +``` + +## State Management + +### data-signals + +Define reactive signals (state). + +```html + +
    + + +
    +``` + +### data-json-signals + +Define signals from JSON (useful for server-rendered initial state). + +```html +
    +``` + +### data-bind:[attr] + +Two-way binding between element attribute and signal. + +```html + + + + + + + + +``` + +### data-computed + +Derived/computed values from signals. + +```html +
    + + Total: +
    +``` + +### data-init + +Initialize/run code when element loads. + +```html +
    +``` + +### data-effect + +Run side effects when dependencies change. + +```html +
    +``` + +## DOM Updates + +### data-text + +Set element's text content. + +```html +placeholder +placeholder +``` + +### data-show + +Conditionally show/hide element. + +```html +
    Welcome back!
    +
    You have items
    +``` + +### data-class + +Dynamically add/remove classes. + +```html + +
    + + +
    +``` + +### data-attr:[attribute] + +Set any attribute dynamically. + +```html + + +Link +``` + +### data-style:[property] + +Set inline styles. + +```html +
    +
    +``` + +### data-ref + +Create a reference to the element. + +```html + + +``` + +## Morphing Control + +### data-ignore + +Skip element during all morphing operations. + +```html +
    + +
    +``` + +### data-ignore-morph + +Preserve element content across morphs but allow attribute updates. + +```html + +``` + +### data-preserve-attr:[attr] + +Keep specific attribute values during morph. + +```html + +``` + +## Backend Actions + +Actions are prefixed with `@` and execute in a sandboxed environment. + +### HTTP Methods + +```html + + + + + +``` + +### Action Options + +```html + + + + + +``` + +## Loading States + +### data-indicator + +Show element while request is in progress. + +```html + + +``` + +## Pro Attributes (Datastar Pro) + +- `data-animate` - Animation helpers +- `data-custom-validity` - Form validation +- `data-on-raf` - requestAnimationFrame triggers +- `data-on-resize` - Resize observer triggers +- `data-persist` - Persist signals to storage +- `data-query-string` - Sync with URL query params +- `data-replace-url` - Update URL without navigation +- `data-scroll-into-view` - Scroll element into view +- `data-view-transition` - View transition API helpers + +## Expression Syntax + +Expressions in attributes support JavaScript with access to: +- All signals as variables +- `$el` - Current element +- `$refs` - Element references +- `evt` - Event object (in event handlers) + +```html +
    + + + +
    +``` diff --git a/.agents/skills/datastar/reference/sse.md b/.agents/skills/datastar/reference/sse.md new file mode 100644 index 0000000..0f63f07 --- /dev/null +++ b/.agents/skills/datastar/reference/sse.md @@ -0,0 +1,233 @@ +# SSE Events Reference + +Datastar uses Server-Sent Events (SSE) for backend-to-frontend communication. The backend streams events that update the DOM and signals. + +## Content Type + +``` +Content-Type: text/event-stream +``` + +## Event Format + +``` +event: +data: +data: + +``` + +Note: Events are separated by double newlines. + +## Event Types + +### datastar-patch-elements + +Updates DOM elements through morphing. + +**Basic Usage:** +``` +event: datastar-patch-elements +data: elements
    Hello world!
    + +``` + +**Multi-line HTML:** +``` +event: datastar-patch-elements +data: elements
    +data: elements

    Alice

    +data: elements

    Software Engineer

    +data: elements
    + +``` + +**Options:** + +| Key | Default | Description | +|-----|---------|-------------| +| `selector` | - | CSS selector for target element | +| `mode` | `outer` | How to apply the patch | +| `namespace` | - | `svg` or `mathml` for XML content | +| `useViewTransition` | `false` | Enable view transitions | +| `elements` | - | The HTML content | + +**Modes:** + +| Mode | Description | +|------|-------------| +| `outer` | Replace entire element (morph outer HTML) | +| `inner` | Replace children only (morph inner HTML) | +| `replace` | Hard replace outer HTML (no morph) | +| `prepend` | Add as first child | +| `append` | Add as last child | +| `before` | Insert before element | +| `after` | Insert after element | +| `remove` | Remove the element | + +**Examples:** + +``` +event: datastar-patch-elements +data: mode inner +data: selector #messages +data: elements
    New message!
    + +``` + +``` +event: datastar-patch-elements +data: mode append +data: selector #list +data: elements
  • New item
  • + +``` + +``` +event: datastar-patch-elements +data: mode remove +data: selector #old-element + +``` + +### datastar-patch-signals + +Updates reactive signals (state) on the page. + +**Basic Usage:** +``` +event: datastar-patch-signals +data: signals {count: 42, name: "Alice"} + +``` + +**Options:** + +| Key | Default | Description | +|-----|---------|-------------| +| `signals` | - | JavaScript object with signal values | +| `onlyIfMissing` | `false` | Only set if signal doesn't exist | + +**Remove signals** by setting to `null`: +``` +event: datastar-patch-signals +data: signals {oldSignal: null} + +``` + +**Conditional creation:** +``` +event: datastar-patch-signals +data: onlyIfMissing true +data: signals {defaultValue: 0} + +``` + +## Backend SDK Examples + +### Go + +```go +import "github.com/starfederation/datastar/sdk/go" + +func handler(w http.ResponseWriter, r *http.Request) { + // Read signals from request + signals := datastar.ReadSignals(r) + + // Create SSE writer + sse := datastar.NewSSE(w) + + // Patch elements + sse.PatchElements("#result", "
    Updated!
    ") + + // Patch with options + sse.PatchElements("#list", "
  • Item
  • ", + datastar.WithMode("append")) + + // Patch signals + sse.PatchSignals(map[string]any{ + "count": signals.Count + 1, + "status": "complete", + }) +} +``` + +### Python + +```python +from datastar import SSE, read_signals + +def handler(request): + signals = read_signals(request) + + sse = SSE() + + sse.patch_elements("#result", "
    Updated!
    ") + sse.patch_signals({"count": signals.count + 1}) + + return sse.response() +``` + +### TypeScript + +```typescript +import { SSE, readSignals } from '@starfederation/datastar'; + +export async function handler(req: Request): Promise { + const signals = await readSignals(req); + + const sse = new SSE(); + + sse.patchElements("#result", "
    Updated!
    "); + sse.patchSignals({ count: signals.count + 1 }); + + return sse.response(); +} +``` + +## Streaming Multiple Events + +A single response can contain multiple events: + +``` +event: datastar-patch-signals +data: signals {status: "loading"} + +event: datastar-patch-elements +data: selector #progress +data: elements
    + +event: datastar-patch-signals +data: signals {status: "complete", progress: 100} + +event: datastar-patch-elements +data: selector #result +data: elements
    Done!
    + +``` + +This enables progressive updates during long operations. + +## Compression + +SSE streams compress extremely well due to repetitive DOM patterns. Enable Brotli compression on your server for significant bandwidth savings (up to 200:1 ratios). + +## Connection Management + +- SSE connections are persistent +- Browser automatically reconnects on disconnect +- Use `data-on-interval` for polling patterns instead of keeping connections open for infrequent updates + +## Error Handling + +Send error messages by patching elements or signals: + +``` +event: datastar-patch-elements +data: selector #error +data: elements
    Something went wrong!
    + +event: datastar-patch-signals +data: signals {error: "Failed to save", isLoading: false} + +``` diff --git a/.agents/skills/kita-html/SKILL.md b/.agents/skills/kita-html/SKILL.md new file mode 100644 index 0000000..4b48dbb --- /dev/null +++ b/.agents/skills/kita-html/SKILL.md @@ -0,0 +1,402 @@ +--- +name: kita-html +description: KitaJS HTML — TypeScript JSX rendering library. Use when building HTML templates, creating server-side components, handling async rendering, streaming HTML with Suspense, or integrating with Fastify for HTML responses. Covers type-safe JSX components, layouts, async/await patterns, error boundaries, and Fastify HTML plugin usage. +--- + +# KitaJS HTML Skill + +KitaJS HTML is a modern TypeScript JSX framework for server-side HTML rendering. It provides type-safe component definitions, async/await support, streaming with Suspense, and seamless Fastify integration. + +## Critical Limitation: Server-Side Only + +⚠️ **KitaJS HTML is for server-side rendering only.** You cannot write client-side JavaScript logic inside components like you do in React. + +### What You CANNOT Do: +- Use `useState`, `useEffect`, `useCallback`, or any React hooks +- Add event handlers (`onClick`, `onChange`, etc.) with inline logic +- Use browser APIs (`localStorage`, `window`, `document`) +- Create stateful components or client-side interactivity + +```tsx +// ❌ WRONG - This does not work in KitaJS HTML +function Counter() { + const [count, setCount] = useState(0); + + return ( + + ); +} +``` + +### What You CAN Do: +- Render HTML structure server-side +- Fetch data from databases/APIs +- Process and transform data +- Output static HTML +- Use `data-*` attributes for client-side enhancement (with separate JS) + +```tsx +// ✅ CORRECT - Pure server-side rendering +function Counter(props: { initialCount: number }) { + return ( +
    +

    Current count: {props.initialCount}

    + +
    + ); +} + +// Then in a separate JS file, handle client-side events: +// document.querySelector('[data-action="increment"]').addEventListener('click', ...) +``` + +### Adding Client-Side Interactivity: +Use `data-*` attributes and separate JavaScript files: + +```tsx +// Server component +function FormField(props: { id: string; label: string }) { + return ( +
    + + +
    + ); +} + +// Separate client scripts/validators.js +document.querySelectorAll('[data-validate="email"]').forEach(input => { + input.addEventListener('blur', () => { + // Validate email client-side + }); +}); +``` + +--- + +## Core Concepts + +### 1. Basic JSX Components + +KitaJS HTML uses JSX syntax to create type-safe HTML components. Components are functions that return HTML strings. + +```tsx +import { html } from '@kitajs/html'; + +// Simple component +function Greeting(props: { name: string }) { + return html`

    Hello, ${props.name}!

    `; +} + +// Using in a layout +const page = html` +
    + ${Greeting({ name: 'World' })} +
    +`; +``` + +**Key Points:** +- JSX is compiled to function calls +- Components receive props as the first argument +- HTML strings are tagged templates +- Props are type-safe with TypeScript interfaces + +### 2. Layout Components + +Create reusable layout wrappers for HTML structure: + +```tsx +interface LayoutProps { + title?: string; + head?: string; + children: string; +} + +export function Layout(props: LayoutProps) { + return ( + <> + {''} + + + + + {props.title || 'Default Title'} + + {props.head} + + {props.children} + + + ); +} + +// Usage +const page = ( + }> +

    Welcome

    +
    +); +``` + +**Layout Patterns:** +- Use fragments `<>` to return multiple root elements +- Include DOCTYPE as a string literal: `{''}` +- Nest `head` slot for metadata and styles +- Nest `children` slot for page content + +### 3. Async Components & Suspense + +Handle asynchronous data fetching in components: + +```tsx +import { Suspense } from '@kitajs/html'; + +// Async component fetches data +async function UserCard(props: { userId: string }) { + const user = await fetchUser(props.userId); + return ( +
    +

    {user.name}

    +

    {user.email}

    +
    + ); +} + +// Wrap async components with Suspense for streaming +const page = ( + + Loading user...
    }> + + + +); +``` + +**Suspense Behavior:** +- Fallback UI renders first, then streams the async component +- Ideal for server-side rendering with data fetching +- Enables progressive HTML streaming + +### 4. Conditional Rendering & Loops + +Use JavaScript expressions for logic: + +```tsx +interface ListProps { + items: Array<{ id: string; name: string }>; + empty?: boolean; +} + +function ItemList(props: ListProps) { + return ( +
      + {props.empty ? ( +
    • No items found
    • + ) : ( + props.items.map(item => ( +
    • {item.name}
    • + )) + )} +
    + ); +} +``` + +### 5. Fastify Integration + +Use the Fastify HTML plugin to send rendered HTML: + +```tsx +import Fastify from 'fastify'; +import { fastifyHtmlPlugin } from '@kitajs/fastify-html-plugin'; + +const app = Fastify(); +app.register(fastifyHtmlPlugin); + +// Simple HTML response +app.get('/page', (req, reply) => + reply.html( + +

    Welcome to my site

    +
    + ) +); + +// Streaming with Suspense +app.get('/stream', (req, reply) => + reply.html( + + Loading...
    }> + + + + ) +); + +app.listen({ port: 3000 }); +``` + +**reply.html() options:** +- Automatically sets `Content-Type: text/html; charset=utf-8` +- Supports streaming with Suspense components +- Returns a Promise that resolves when HTML is sent +- `rid` parameter enables request-specific streaming context + +## Common Patterns + +### Error Boundaries + +Create error boundary components to catch rendering errors: + +```tsx +async function PageWithErrorBoundary(props: any) { + try { + return ( + + + + ); + } catch (err) { + return ( + +
    +

    Something went wrong

    +

    {err instanceof Error ? err.message : 'Unknown error'}

    +
    +
    + ); + } +} +``` + +### Props with Children + +Define components that accept nested content: + +```tsx +interface CardProps { + title: string; + children: string; // Content inside the card +} + +function Card(props: CardProps) { + return ( +
    +

    {props.title}

    +
    {props.children}
    +
    + ); +} + +// Usage with JSX children +const page = ( + +

    This content goes inside the card

    +
    +); +``` + +### Conditional Elements + +Use ternary operators and logical AND for conditional rendering: + +```tsx +function Dashboard(props: { isAdmin: boolean; items: Item[] }) { + return ( +
    + {props.isAdmin && } + {props.items.length > 0 ? ( + + ) : ( + + )} +
    + ); +} +``` + +## Best Practices + +1. **Type Your Props**: Use TypeScript interfaces for all component props + ```tsx + interface ButtonProps { + label: string; + href: string; + variant?: 'primary' | 'secondary'; + } + ``` + +2. **Compose Components**: Build larger UIs from smaller, reusable pieces + ```tsx + function Page() { + return ( + +
    + +