Files
inkreach-teable-cli/skills/inkreach-teable-plugin/SKILL.md
T
yeuimu 517dde9dee feat(skill): native-form 速查文档 + 路由行 (0.1.8)
- dynamic-plugins/native-form.md: 咨询题直答型(两档注入 API + 多步表单
  copy-paste 示例 + 边界),解决 eval-6 历史失败(agent 把咨询当开发、跑飞)
- SKILL.md 模块地图加 3b 行:问'能不能/怎么做原生表单'先读它,直接作答
- 实测:plugin 技能 N=3 全绿(P1 25/25, P2 25/25, P3 24/25),
  native-form-routing 三轮全过(此前两轮全败)
2026-09-30 07:16:33 +00:00

9.0 KiB

name, description
name description
inkreach-teable-plugin Develop dynamic React component plugins for inkreach-teable — IIFE bundles loaded at runtime via window.__TEABLE_HOST__, no iframe sandbox, full host API access. Scaffold with plugin-cli, build with webpack, deploy via upload-bundle API, register via definePlugin + PluginContext. Positions: contextMenu, panel, view, dashboard, route. Use this whenever the user mentions plugins, 插件, batch-edit, context-menu actions, panel widgets, dashboard charts, custom views, plugin.json, definePlugin, bundle plugin, plugin-cli, plugin-sdk, plugin-host, or window.__TEABLE_HOST__. Even if the user just says "add a right-click action" or "make a side panel" or "I need a dashboard widget" — or wants the native form embedded or extended inside a plugin (原生表单, 表单注入, 表单虚拟字段, custom form layout, NativeForm, NativeFormScope, form panel) — load this skill. Reference example at `plugins/batch-edit-pro/` (on the deployment host: `/root/deployments/inkreach-teable/plugins/batch-edit-pro/`).

inkreach-teable dynamic plugin development

This skill develops React component plugins that load at runtime without rebuilding the host Next.js app. Plugins are IIFE bundles — they ship as plain JS that executes in the host page and accesses host APIs through window.__TEABLE_HOST__.

Contract: A plugin = a webpack IIFE bundle (dist/main.js + optional dist/styles.css) + plugin.json manifest, uploaded to the host via POST /api/plugin/:id/upload-bundle. It runs in the same React tree as the host and has full access to the host's SDK, UI library, and OpenAPI helpers.

Critical constraint: no RUNTIME imports from @teable/sdk, @teable/ui-lib, @teable/openapi — webpack maps them to window.__TEABLE_HOST__.* externals, so a value import compiles to undefined at runtime. import type { X } from '@teable/...' IS fine (erased at compile time). react/react-dom may be imported ONLY when your webpack externals maps them to window.__TEABLE_HOST__.React/ReactDOM (see the batch-edit-pro webpack config). For everything else, read from the host global: const { useFields } = window.__TEABLE_HOST__.SDK. See dynamic-plugins/troubleshooting.md Pitfall 1 for details and symptoms.

0. Two efficiency rules (read first)

  • Trust these docs. The dynamic-plugins/ documents are the verified contract for this deployment. Answer plugin capability/behavior questions directly from them — do NOT re-derive behavior by spelunking nestjs-backend/src or the host source tree; that burns minutes and adds nothing. Source diving is only for debugging a concrete discrepancy the docs can't explain.
  • Don't upload demo plugins to verify knowledge. For how-to/consultation questions, a written answer (with code) is the deliverable. Only upload a bundle when the user actually asked to deploy — test uploads belong in a throwaway sandbox base, never the production plugin registry.

1. When to use this skill

  • User says "写个插件 / 做个插件 / make a plugin" — yes.
  • User wants a context-menu action (batch edit / process selected records) — yes.
  • User wants a panel gadget (real-time stats / summary) — yes.
  • User wants a custom view (kanban, gallery, timeline) — yes.
  • User wants a dashboard chart — yes.
  • User wants to embed / extend the NATIVE FORM in a plugin (原生表单, form injection, virtual form fields, custom multi-step form with native field editors, host.NativeForm, NativeFormScope) — yes, see sdk-api.md.
  • User mentions definePlugin, plugin-cli, plugin-sdk, window.__TEABLE_HOST__, bundle plugin — yes.
  • User wants to manage tables/automations (not plugins) — use inkreach-teable skill instead.

2. Module map (read in order when developing)

Stage File Read it when...
1. Understand architecture dynamic-plugins/overview.md always first — how window.__TEABLE_HOST__, PluginLoader, PluginSlot work
2. Dev workflow dynamic-plugins/dev-workflow.md when building a plugin — the step-by-step recipe from scaffold to deploy
3. SDK API dynamic-plugins/sdk-api.md when writing plugin logic — definePlugin, PluginContext, events, storage, useSelection, native form embedding (NativeForm Tier-1 injection / NativeFormScope+useNativeForm Tier-2 kit), NativeGrid
3b. Native form quick answer dynamic-plugins/native-form.md when the user ASKS whether/how to embed native field editors, custom form layout, or multi-step forms — read this FIRST and answer directly (consultative questions need no scaffold/build)
4. Deployment dynamic-plugins/deployment.md when uploading + enabling + authority matrix permissions
5. Troubleshooting dynamic-plugins/troubleshooting.md before writing logic — common pitfalls (host globals, developer whitelist, AM, empty states)
Ref: batch-edit-pro plugins/batch-edit-pro/ the canonical example — contextMenu plugin, full React + SDK

3. Quick decision: which position?

User wants Position definePlugin registration
Right-click action on records ContextMenu ctx.addContextMenuAction(id, component)
Side gadget while viewing a table Panel ctx.addPanel(id, component)
A new view type View ctx.addView(id, component)
Card on the space dashboard Dashboard ctx.addDashboardWidget(id, component)
A new page/route Route ctx.addRoute(path, component)

4. Auth & test credentials

  • Endpoint: http://localhost:3003 (backend; :3000 is taken by an unrelated docker container on this host — see AGENTS.md), frontend http://localhost:3002.
  • Plugin upload requires a plugin_developer row for the uploading user (INSERT INTO plugin_developer (id, user_id, email, created_time, created_by) VALUES ('plgd<name>', '<userId>', '<email>', now(), '<userId>')).
  • Sandbox base for any hands-on test: create a throwaway base (POST /api/base with {"spaceId":"<spaceId>","name":"Sandbox(可删)"}), do all table/install testing inside it, and delete it (DELETE /api/base/:id) when done. Never hardcode base/table IDs here — they rot; resolve them at runtime (inkreach-teable base list) instead.
  • Auth: PAT (Authorization: Bearer <PAT>; upload-bundle supports it) or session cookie auth_session. Note: some admin-gated routes (e.g. DELETE /api/plugin/:id) pass with an admin session but 403 with a plain PAT even for the same user — fall back to the session for those.

5. Start here

For any plugin request, read dynamic-plugins/overview.md then dynamic-plugins/dev-workflow.md. Reference plugins/batch-edit-pro/ for a complete working example.

6. Plugin backend services — MUST use the same-origin proxy (2026-08-21)

The instance is publicly served at https://table.inkreach.cc (frp + cloud Caddy); browsers on the public internet CANNOT reach LAN addresses like http://192.168.124.92:8001. Plugin bundles that need their own backend service MUST call it through the session-gated dynamic proxy instead:

  • URL contract: fetch('/api/plugin-proxy/<service>/<path...>') — same-origin, works from LAN and public internet alike. NEVER hardcode LAN IPs / localhost in a bundle.
  • Registry: one line in PLUGIN_API_SERVICES (JSON, in apps/nextjs-app/.env AND .env.development), then restart teable-backend.service. No rebuild, no code change. Example: PLUGIN_API_SERVICES={"track":"http://127.0.0.1:8001"}
  • Implementation: NestJS controller apps/nestjs-backend/src/features/plugin/plugin-proxy.controller.ts (global AuthGuard gates login; upstream whitelist = registry; session cookies are never forwarded upstream). Do NOT move this to a Next.js pages-router API route — the /api/:path* rewrite in next.config.js outranks catch-all file routes, and files outside pages/api/ get compiled as SSR pages (observed on Next 16.1.6/turbopack).
  • CSP: same-origin fetches need nothing; truly external origins must be declared in the plugin manifest apiOrigins (appended to connect-src by plugin-runtime.controller.ts).
  • Re-uploading a plugin zip OVERWRITES the bundle — re-check for hardcoded LAN URLs afterwards (grep -o 'http://192.168[^\"']*' <bundle>/main.js). Live example: apps/nestjs-backend/.assets/plugins/bundle/track-panel/ (patched from LAN URL to /api/plugin-proxy/track; backups kept as main.js.bak-*).
  • Root fix (2026-08-22 recurrence): a developer re-uploaded track-panel at 02:23 with the LAN URL back in the source-built bundle, breaking public search with a CORS error within hours of the last patch. Patching main.js on disk only fixes the CURRENT artifact — the plugin SOURCE must use /api/plugin-proxy/track before building/uploading, otherwise every re-upload reintroduces the bug. The backend serves bundle files from disk per-request, so a disk patch takes effect without a backend restart.