AgentSkillsCN

wire-feature

在叙事卡中端到端连接新旧钩子动作:useGameState → Index.tsx → 屏幕Props → UI触发 → 单元测试。在添加游戏机制、按钮、模态框,或任何新的交互行为时使用。内置验证脚本,可对三层进行全方位检查。触发词:连接、布线、钩子动作、打通、添加功能、新机制、Index.tsx。

SKILL.md
--- frontmatter
name: wire-feature
description: "Wire a new or existing hook action end-to-end in Narrative Cards: useGameState → Index.tsx → screen props → UI trigger → unit test. Use when adding a game mechanic, button, modal, or any new interactive behaviour. Includes a verification script that checks all three layers. Trigger words: wire, wiring, hook action, connect, add feature, new mechanic, Index.tsx."
argument-hint: "Action name to wire (e.g. addFact, resolveScene)"
user-invocable: true

Wire a Feature (Three-Layer Wiring)

Every interactive feature in Narrative Cards must exist in all three layers simultaneously. Any layer left missing creates dead code.

When to Use

  • Adding a new hook action to useGameState.ts
  • Adding a button or modal that calls a game rule
  • Connecting an existing hook action that's not yet in the UI
  • Auditing whether a new feature is fully wired

Quick Verification Script

Before or after changes, run:

bash
node .github/skills/wire-feature/scripts/check-wiring.cjs <actionName>

This checks:

  • Layer 1a: action const defined in useGameState.ts
  • Layer 1b: action included in the hook's return {} object
  • Layer 2a: action destructured in Index.tsx
  • Layer 2b: action passed as a named prop (e.g., onAddFact={addFact})
  • Layer 3a: prop received in a screen Props interface
  • Layer 3b: prop called in JSX (a UI trigger exists)
  • Test: a unit test exists for the action

Example output:

code
✅ Layer 1a: 'addFact' defined in useGameState.ts
✅ Layer 1b: 'addFact' included in hook return object
❌ Layer 2a: 'addFact' destructured in Index.tsx
   → Destructure from useGameState(): const { ..., addFact } = useGameState();

Step-by-Step Procedure

Layer 1 — Hook (src/hooks/useGameState.ts)

  1. Add the action as a useCallback closure:
typescript
const addFact = useCallback((text: string, creatorId: string) => {
  setGameState((prev) => ({
    ...prev,
    facts: [
      ...prev.facts,
      {
        id: crypto.randomUUID(),
        text,
        creatorId,
        status: "open",
        isSecret: false,
        revealed: true,
        complications: [],
        callbacks: 0,
        sceneCreated: prev.sceneCount,
      },
    ],
  }));
}, []);
  1. Add the action name to the return {} object at the bottom of the hook.

Golden rules:

  • Always spread with { ...prev, ... } — never mutate directly
  • Cap meters: Math.min(10, Math.max(0, prev.tension + delta))
  • Append to arrays: [...prev.arr, newItem] — never prev.arr.push()

Layer 2 — Router (src/pages/Index.tsx)

  1. Destructure from useGameState():
typescript
const { ..., addFact } = useGameState();
  1. Pass as a prop in all relevant case blocks. If the screen is GameplayScreen, add the prop to BOTH 'play' AND 'scene-end' cases — they render the same component:
typescript
case 'play':
case 'scene-end':
  return (
    <GameplayScreen
      ...
      onAddFact={addFact}
    />
  );

Layer 3 — UI (screen component)

  1. Add the prop to the Props interface:
typescript
interface GameplayScreenProps {
  ...
  onAddFact: (text: string, creatorId: string) => void;
}
  1. Add a visible trigger in JSX — button, form submit, or modal confirm:
typescript
<Button onClick={() => onAddFact(inputText, currentPlayer.id)}>
  Add Fact
</Button>
  1. If a modal is involved: create it in src/components/game/, import it in the screen, render it conditionally with a useState toggle.

Unit Test

Add to src/hooks/useGameState.test.ts:

typescript
describe("addFact", () => {
  it("adds a new open fact to state", () => {
    const result = startPlay();
    act(() =>
      result.current.addFact(
        "The key is missing",
        result.current.gameState.players[0].id,
      ),
    );
    expect(result.current.gameState.facts).toHaveLength(1);
    expect(result.current.gameState.facts[0].status).toBe("open");
  });
});

Update the Wiring Map

After wiring, update the Current Action × UI Map table in feature-wiring.instructions.md.

Completion Criteria

Run in order — all must pass:

bash
node .github/skills/wire-feature/scripts/check-wiring.cjs <actionName>
npm run lint
npm run build
npm test

If check-wiring.cjs reports failures, fix each one before running lint/build/test.