mcpField
R
remix-v2-forms-review

remix-v2-forms-review

github.com/existential-birds测试与质量8541 次安装11 周前更新
代码仓库

关于 remix-v2-forms-review

用于审查 Remix v2 表单代码的技能,检查提交方式、待处理状态、文件上传、数据验证及多变更路由模式。它采用上下文相关规则和核验门槛,减少误报。

主要功能

  1. 01Form 与 fetcher 用法检查
  2. 02待处理状态审查
  3. 03上传与 FormData 验证检查
  4. 04多变更路由模式检查
  5. 05证据与例外核验门槛

适用场景

  1. 01审查 Remix 表单改动
  2. 02发现绕过路由 action 的变更
  3. 03检查逐行待处理指示器
  4. 04审计上传和输入验证

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/existential-birds/beagle --skill remix-v2-forms-review

安装后在 Claude Code 中输入/remix-v2-forms-review确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
form-vs-fetcher.md
multi-action-routes.md
pending-state.md
uploads-validation.md

详细介绍

Remix v2 表单代码审查

规范模式请参阅 remix-v2-forms。此技能用于标记违规;配套技能则讲解这些模式。

快速参考

问题类型参考资料
手动使用 fetch()、原生 <form>、错误选择 <Form> 或 useFetcherreferences/form-vs-fetcher.md
使用 useState 设置加载标志、使用 useNavigation 管理每行状态、缺少待处理状态references/pending-state.md
上传时内存使用无上限、缺少 encType、未验证的 FormData、镜像乐观状态references/uploads-validation.md
未采用意图模式而导致路由泛滥、PUT/DELETE 没有 PE 回退方案references/multi-action-routes.md

审查清单

  • 应用内的变更使用 <Form> 或 <fetcher.Form>,绝不使用 fetch() / axios
  • POST 变更时,从 @remix-run/react 导入 <Form>(而不是使用原生 <form>)
  • URL 不应改变时(行切换、内联编辑)使用 useFetcher
  • URL 应改变时(创建、删除后返回列表)使用 <Form> + redirect(...)
  • 待处理状态从 useNavigation() 或 fetcher.state 派生,绝不使用 useState
  • 每行的待处理状态使用该行自己的 fetcher.state(而不是页面全局的 useNavigation)
  • 调用 useNavigation() 时检查 navigation.formAction,以限定为预期路径
  • 乐观 UI 直接读取 fetcher.formData / navigation.formData(不镜像状态)
  • 成功时,action 返回 redirect(...);仅在出错或停留在同一页面时返回 json()
  • 每个文件上传表单都设置 <Form encType="multipart/form-data">
  • unstable_createMemoryUploadHandler 始终设置 maxPartSize;大文件使用磁盘/流处理器
  • FormData 值在传入数据库前经过验证/类型转换(不要使用 form.get(x) as string)
  • 同一条路由上的多个变更使用意图模式,而不是拆分成多条路由
  • method="put|patch|delete" 应注明仅支持 JS,或改写为 POST + 意图
  • 将 nav.formMethod / fetcher.formMethod 与大写字符串("POST"、"GET")比较;v2 的 v2_normalizeFormMethod 默认返回大写值——使用 === "post" 会导致条件永远不匹配。

有效模式(不要标记)

以下是正确的 Remix v2 用法,不应报告:

  • 不带 action 属性的 <Form>——按照约定,会向当前 URL 提交;action 是可选的。
  • GET <Form>——适用于搜索/筛选界面;会将表单字段作为 URL 搜索参数传给 loader,且不会调用 action。大多数“规范性”规则(意图、redirect、encType)仅适用于 POST 表单。
  • 在同一页面上使用多个 useFetcher() 实例——每次调用都会返回独立的提交通道;这是针对不同行进行并行变更的有意设计。
  • 在事件处理程序中使用 useSubmit()——适用于自动保存、键盘快捷键或 onChange 触发器的正确程序化提交方式。
  • 在提交期间读取 fetcher.formData——这是预期用法,也是规范的乐观数据来源。
  • 提交后 useActionData 中的数据仍然存在——这是已知行为;它会返回上一次 action 的结果,直到下一次导航或 action。
  • 在 <Form> 上使用 navigate={false}——会将其转换为 fetcher 表单;效果等同于不持有 fetcher 引用的 <fetcher.Form>。
  • parseMultipartFormData / 上传处理器上的 unstable_ 前缀——在 v2 中是永久保留的;不要将其标记为“不稳定 API”。

上下文相关规则

仅当满足所列条件时才标记:

问题仅当以下情况时标记
使用原生 <form> 而非 <Form>方法为 POST,且路由有 action — GET 表单和外部 URL 表单均可接受
缺少待处理状态表单为 POST,且组件中任何位置都没有读取 useNavigation() / fetcher.state
创建后 action 返回 json({ ok: true })路由是“/new”或创建界面 — 同页编辑表单返回 JSON 是合理的
method="put" / "patch" / "delete"该界面需要渐进增强(公共应用)— 管理后台/仅限 JS 的工具可在文档中说明后选择不采用
unstable_createMemoryUploadHandler 没有大小限制上传内容包含用户控制的文件(而非固定大小的内部工件)
每种变更操作使用单独路由这些变更操作针对相同资源,且身份验证规则兼容 — 具有不同规则的同级资源不在此列
使用 useNavigation() 时未设置 formAction 过滤条件组件包含其他导航界面(侧边栏 <Link>、同级表单),这些界面会触发误报
将 fetcher.formData 镜像到状态中被镜像的值用于驱动用户可见元素(按钮标签、计数、切换开关)— 本地的“is-editing”标记无关

硬性门槛(撰写问题前)

按顺序执行。在以下所有门槛都通过前,不要撰写面向用户的问题报告,且这些门槛必须适用于你即将报告的这一批问题。

  1. 位置证据 — 通过条件: 每个问题都列出仓库路径,以及你读取过的文件中的行范围或简短的逐字引文(不得凭记忆或仅根据 diff 猜测)。注明路由模块、组件,以及存在时的 action。

  2. 豁免检查 — 通过条件: 对于每个问题,你都能用一行说明它为何不属于 有效模式(不要标记) 中的豁免情况,也不符合 上下文相关规则 中的任何适用条目。

  3. 表单方法检查 — 通过条件: 在标记缺少 intent、缺少 encType、缺少 redirect 或缺少待处理状态之前,已确认表单的 method="post"(或 put|patch|delete)。GET 表单可用于搜索/筛选,并会触发 loader,而非 action — 将 POST 表单规则套用到这些表单上会造成误报。

  4. 协议 — 通过条件: 已针对本次审查完成 review-verification-protocol 中的报告前验证清单。

补充文档

何时加载参考文档

  • 审查调用 fetch() / axios / 原生 <form> 的表单,或在 <Form> 与 useFetcher 之间进行选择 → form-vs-fetcher.md
  • 审查加载标记、旋转图标、禁用按钮逻辑、逐行待处理状态 → pending-state.md
  • 审查文件上传、unstable_* 处理器、FormData 解析、乐观 UI → uploads-validation.md
  • 审查包含多个变更操作、intent 字段、PUT/DELETE 方法的路由 → multi-action-routes.md

审查问题

  1. 应用内的每项变更是否都通过路由 action 完成(没有手动调用 fetch())?
  2. <Form> 与 useFetcher 的选择是否取决于 URL 是否应该改变?
  3. 待处理状态是否由 useNavigation() / fetcher.state 派生(绝不使用 useState)?
  4. 每行的加载指示器是否绑定到该行自己的 fetcher.state(而非页面级的 useNavigation)?
  5. 文件上传表单是否设置了 encType="multipart/form-data" 并使用了有界处理器?
  6. FormData 的值在传入数据库前是否经过验证?
  7. 同一资源上的多个变更是否使用意图模式,而非拆分到不同路由?
  8. POST 表单是否至少有一个可用于渐进增强的实际提交按钮?

误报说明

  • 在非路由组件中渲染的 <Form> 仍然关联到最近路由的 action——在标记“缺少 action”之前,请先查看路由文件。
  • 成功提交后 useActionData() 返回数据是预期行为;这些数据会持续存在,直到下次导航。只有在成功横幅无条件显示且没有关闭方式时,才应标记。
  • 以别名导入 Form 的代码(例如 import { Form as RemixForm }) 仍然是 Remix 组件——应根据导入来源匹配,而不是本地名称。

提交问题前

完成 硬性检查(尤其是检查 4),然后只报告仍通过 review-verification-protocol 报告前检查的问题。

评论

支持 Markdown