@kentcdodds: 这太不可思议了。你可以在@kodykoala内运行一个全栈@remix_run应用:https://kody.codes/@kentcdodds/packag…

X AI KOLs Following 工具

摘要

Kent C. Dodds宣布推出一个针对Kody的包应用套件,它能够运行全栈Remix应用,并集成了设计令牌、PWA支持、错误处理和实时同步等功能。

这太不可思议了。你可以在@kodykoala内运行一个全栈@remix_run应用:https://t.co/1ByZtRWdjX Kody包运行在动态工作器中。这真是太酷了。 它可以执行你的Kody账户能做的任何事情。构建个人软件的最简单方式。https://t.co/mZGKwm1VG5
查看原文
查看缓存全文

缓存时间: 2026/09/16 05:54

这太疯狂了。你可以在 @kodykoala 内运行一个完整的 @remix_run 应用:https://t.co/1ByZtRWdjX Kody 包运行在动态 Worker 中。简直酷毙了。它拥有执行你 Kody 账户所能做任何事情的权限。这是构建个人软件最简单的方式。https://t.co/mZGKwm1VG5


@kentcdodds/package-app-kit — Kody 公共包

来源:https://kody.codes/@kentcdodds/package-app-kit
← 公共包 (https://kody.codes/community)

设计令牌、PWA 安装/更新、关于/版本信息、缓存辅助工具,以及用于 Kody 包应用的可选实时笔记同步功能。
仓库 (https://kody.codes/@kentcdodds/package-app-kit)
文件 (https://kody.codes/@kentcdodds/package-app-kit/tree/main)

  • 应用 (https://kody.codes/community?category=apps)
  • app - pwa - design - cache - kit

版本 0.1.0
许可
发布于 2026年9月15日
固定提交 7ca952c
评分 暂无评分
分叉 0
适配工作量 —

@kentcdodds/package-app-kit

Kody 包应用的运行时设计/PWA 工具包。

意图

为 Kent(以及未来的 @kody 消费者)提供一个精简的私有工具包,让每个包应用都能共享:

  • CSS 自定义属性设计令牌(包括 间距比例 --pak-space-18 + pak-stack/pak-cluster/pak-inset)和实用的原生 UI(包括一个受 sonner 启发 的 toast 组件)
  • 按压反馈 / 乐观 UI: 点击时立即显示挂起状态;忙碌标签使用 spin-delay 计时(无闪烁)
  • ErrorBanner: 用于验证 / 关于页面 / SSR 错误的共享警告横幅(app/ui/error-banner.tsx + ./error-display
  • 零累积布局偏移(CLS)偏好: “更新可用“是一个固定的、可关闭的 toast(不是流内横幅);临时 UI 不占用文档流
  • Header 安装图标(Lucide download,保留槽位,无 CLS)触发原生安装提示(beforeinstallpromptevent.prompt()),仅在 UA 确实是 iOS 时(固定覆盖层)显示 iOS 添加到主屏幕。为不直观/仅图标控制添加工具提示。
  • /about 页面,包含提交 SHA/消息、提交/发布日期 + 相对时间、包代码链接,以及手动更新检查——通过 record-version 模式实现(不放在首页上)。
  • Remix 示例 实时演示位于 app/ 下({ fetch } 重载包装器 + remix/ui 岛屿),展示令牌、笔记、安装、关于和更新检查功能。
  • 访问时检查更新 → 刷新提示
  • 用于破坏性操作的双重检查辅助工具(两步 + 输入确认;笔记使用 NotesDemo + DoubleCheckButton,支持乐观 UI 和 PE)
  • packageStorage 模式迁移./storage-migrations)—— 稳定的工具包子路径,重新导出 @kentcdodds/package-storage-migrations(版本键、有序的 up 步骤、隔离的记忆化运行器;调用者始终传递 storage)
  • 可选的实时笔记同步./realtime + ./realtime-client)—— Worker broadcastNotesChanged/handleRealtimeEvent;浏览器端 connectPackageRealtime(参见 AGENTS L3
  • Lucide SVG 图标(ISC 许可)—— 通过 scripts/add-lucide-icon.mjs 签入 app/icons/(无完整 Lucide 运行时依赖)
  • 客户端缓存辅助工具(TanStack Query 核心 + IndexedDB)和服务器缓存(@epic-web/cachified + packageStorage
  • 带有僵尸防护的服务 Worker 辅助工具(版本化缓存、清理、谨慎的 skipWaiting/clients.claim,注销过期的 SW)
  • 桌面 + 移动端友好的布局(流体宽度、大型点击目标、无移动端专属文案)

入门模板位于此仓库中starter-remix/(脚手架工具会将该目录嵌入到新的 Artifacts 包中并重命名占位符)。没有单独的 @kentcdodds/package-app-starter 包。此包自身的实时演示(kody.appapp/router.ts)是 Remix 导出形状,用于工具包展示,不是脚手架工具复制的内容。归档的 fetch 模板:starter-fetch/。归档的 Remix 前工具包演示:archive/fetch-demo/。目前为私有 @kentcdodds;稍后提升至 @kody

工具包演示品牌

实时工具包演示使用赛车手 Kody 作为应用内徽标(半身头部、全身主角)以及列表/收藏夹/PWA 标志(赛车手半身像——非 Remix R + 彩虹条纹符号)。设计令牌默认为 Remix-dark(#0a0a0a/#121212),主色为青色,条纹为次要色。脚手架生成的应用仍会包含一个占位符 .kody/icon——请根据脚手架后检查清单生成独特的标志。私有托管应用:通过 kody.codes 上的打开包应用打开。当你需要新的会话或 service worker 时,从包页面重新打开,然后在更新 toast 出现时选择刷新

应用内徽标: 从返回 image/png 的应用路由提供 header/hero/favicon 标志(与 iconPngResponse//icons/... 相同的模式),并在标记中指向 routes\....href()。使用 public/ + \_assets 存放 CSS、sw.js 和客户端包。

技术栈说明

实时工具包演示 是一个 Remix 示例,位于框架无关的宿主合同下(每个条目都是挂载剥离路径上的 fetch 处理器):

"kody": {
  "app": {
    "entry": "./app/router.ts",
    "client": "./app/assets/entry.ts",
    "assets": "./public"
  }
}

演示/入门模板自身拥有的示例样板:根 tsconfig.json 包含 "jsx": "react-jsx" + "jsxImportSource": "remix/ui",在 app/router.ts 中进行重载 + export default { fetch },以及带有浏览器注册表的 clientEntry('kody:app#Name', ...) 岛屿。没有 kody.app.runtime。演示控制器使用相对 ../src/... 路径导入工具包辅助工具(此包不得通过 kody:@kentcdodds/package-app-kit 依赖自身)。

脚手架生成的应用复制 starter-remix/(相同的示例)并通过 kody:@kentcdodds/package-app-kit/... 导入工具包。

库消费者仍然获得 Worker HTML 辅助工具 + 可选的双路径客户端(packageContext.clientModuleUrl,临时的 ./client 启动)。对于 Remix 应用,优先使用平台 remix/ui——不要添加 @remix-run/ui 导入映射或臃肿的 remix 元包作为运行时依赖。

安装 / 导入

import themeCss from 'kody:@kentcdodds/package-app-kit/styles'
import { toastCss, toastHostHtml } from 'kody:@kentcdodds/package-app-kit/ui'
import { createDoubleCheck, createTypeToConfirm, } from 'kody:@kentcdodds/package-app-kit/double-check'
import { installChromeHtml } from 'kody:@kentcdodds/package-app-kit/install'
import { formatAboutPageData, aboutPageHtml } from 'kody:@kentcdodds/package-app-kit/about'
import { kitServedManifestIcons, iconPngResponse } from 'kody:@kentcdodds/package-app-kit/icons'
import { appShellHtml } from 'kody:@kentcdodds/package-app-kit/app-shell'
import { clientBootResponse, clientModuleResponse } from 'kody:@kentcdodds/package-app-kit/client'
import recordVersion from 'kody:@kentcdodds/package-app-kit/record-version'
import checkForUpdate from 'kody:@kentcdodds/package-app-kit/update-check'
import { createPersistedQueryClient } from 'kody:@kentcdodds/package-app-kit/client-cache'
import { createServerCachified } from 'kody:@kentcdodds/package-app-kit/server-cache'
import { renderServiceWorker, buildWebManifest } from 'kody:@kentcdodds/package-app-kit/sw'
import { runPackageStorageMigrations, createMigrationRunner, } from 'kody:@kentcdodds/package-app-kit/storage-migrations'
import { createNotesRealtimeHandler, broadcastNotesChanged, } from 'kody:@kentcdodds/package-app-kit/realtime'
import { connectPackageRealtime } from 'kody:@kentcdodds/package-app-kit/realtime-client'

始终从 packageContext.appBasePath + packageContext.hostedUrl 构建应用内 URL。参见 AGENTS.md 了解采用层级(纯 CSS → fetch 处理器 PWA → Remix → 可选实时 L3)、令牌/类契约、安装客户端要求以及脚手架工具手册。Fetch 处理器应用可以使用 ./app-shellappShellHtml)而不是手动编写文档。

发布后操作

对于此工具包(演示关于/更新检查),调用工具包导出:

import recordVersion from 'kody:@kentcdodds/package-app-kit/record-version'
await recordVersion({ sha: '', message: '', committedAt: '', })

脚手架生成的消费者应用拥有自己的 ./record-version,写入该应用的 packageStorage(参见 starter-remix/src/record-version.ts)。发布脚手架生成的应用后,调用 kody:@scope/app/record-version——而不是工具包的导出。

图标 + 可安装性

列表/身份标志(包、仓库、社区卡片):.kody/icon.png(也支持 .svg/.webp/.jpg/.jpeg)放入。新包使用 .kody/icon——不要脚手架根目录的 community-icon.*。根目录的 icon.*/community-icon.* 保留为永久的平台别名。将 PWA 图标(icons/icon-192.pngpublic/)放在 .kody/ 之外。此工具包包含 .kody/icon.png;脚手架工具生成 .kody/icon.svg(文本),因为 packageSave 无法写入二进制文件。仍然欢迎设计/生成的图标以提升精致度;空的 icons: [] 不会被推广。

PWA 安装(Chromium / Brave URL 栏): 需要一个包含 name/short_namestart_urldisplay 以及实际返回 200 状态码的 192 和 512 尺寸 PNG 图标的清单文件。此工具包嵌入了这些 PNG 并通过 ./iconsiconPngResponse/kitServedManifestIcons)提供它们,因此应用无需提交二进制 PWA 图标文件。仍可设计/生成图标以进行美化;空的 icons: [] 不会被推广。

buildWebManifest 默认 display: "standalone",并接受 fullscreenorientationcategories 和额外的清单字段——当 iOS Safari 手势可能干扰应用时很有用。start_url/scope/id 位于 /packages/<slug>/ 下。每个 HTML 页面必须有 <manifest> 标签。清单响应使用 cache-control: no-store。SW 预缓存首页、/about/notes、图标和清单,并首先通过网络获取清单,不缓存 403 响应。私有托管应用需要通过 kody.codes 进行打开包应用交接。已安装的 PWA 在品牌/图标网络请求在会话 cookie 过期后返回 403 时,可能仍然显示缓存的 HTML 外壳——从包页面重新打开,然后在更新 toast 出现时选择刷新。

入门模板 + 脚手架工具

参见 starter-remix/STARTER.md。执行 create-package-app 后,代理遵循 AGENTS.md 中的脚手架后检查清单(以及落入新包的 AGENTS.md 中的副本):独特的 .kody/icon 列表标志、PWA icons/icon-192.png + icons/icon-512.png、标题/描述、本地 record-version、Remix 示例提醒,以及与标志匹配的清单颜色。

通过一次执行调用创建一个新的私有 Remix 包应用:

import createPackageApp from 'kody:@kentcdodds/package-app-kit/create-package-app'

export default async function main() {
  return await createPackageApp({
    packageName: '@kentcdodds/my-notes', // 或叶子包 `my-notes`
    title: 'My Notes',
  })
}

Lucide 图标(签入的 SVG)

默认图标集:Lucide (https://lucide.dev/)(仓库 (https://github.com/lucide-icons/lucide))——ISC 许可,一致的 24×24 描边图标,纯 SVG。在 https://lucide.dev/icons 浏览名称。

在应用(及入门模板副本)中添加图标,无需打包完整的 Lucide 包:

node scripts/add-lucide-icon.mjs download trash-2 plus
# 写入 app/icons/{name}.svg + {name}.tsx 并镜像到 starter-remix/app/icons/

将生成的 Remix Handle 组件(<IconDownload />)与可见标签一起使用,或在仅图标时与 data-tip + aria-label 一起使用:

import { IconDownload } from '../icons/download.tsx'
import { IconTrash2 } from '../icons/trash-2.tsx'

<button>
  <IconDownload /> 删除
</button>

归属说明作为 app/icons/ATTRIBUTION.md 发布。

packageStorage 迁移

使用版本键和有序迁移来演进持久的 packageStorage 模式。工具包子路径重新导出 @kentcdodds/package-storage-migrations;保持导入 kody:@kentcdodds/package-app-kit/storage-migrations,并始终传递调用者的 storage。

在启动时或第一个需要数据的请求时,为每个 Worker 隔离区运行一次(记忆化):

import { packageStorage } from 'kody:runtime'
import { createMigrationRunner, } from 'kody:@kentcdodds/package-app-kit/storage-migrations'

const ensureSchema = createMigrationRunner({
  storage: packageStorage(),
  versionKey: 'my-app:schema-version',
  migrations: [
    {
      version: 1,
      name: 'notes-to-document',
      async up(storage) {
        const legacy = await storage.get('notes-v1')
        if (Array.isArray(legacy)) {
          await storage.set('notes', { items: legacy })
          await storage.delete?.('notes-v1')
        }
      },
    },
  ],
})

// 在 loader / action 中:
await ensureSchema()

实时演示笔记存储(app/data/notes.ts)展示了此模式的端到端应用。


报告此列表
登录 以报告此列表。

相似文章