@noodleseed/agent-kit 0.62.0 → 0.62.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/manifest.json +251 -251
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +1 -1
  4. package/skills/claude-code/authoring-mcp-servers/SKILL.md +1 -1
  5. package/skills/claude-code/building-mcp-apps/SKILL.md +1 -1
  6. package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +1 -1
  7. package/skills/claude-code/debugging-mcp-delivery/SKILL.md +1 -1
  8. package/skills/claude-code/deploying-mcp-services/SKILL.md +1 -1
  9. package/skills/claude-code/designing-mcp-products/SKILL.md +1 -1
  10. package/skills/claude-code/embedding-mcp-assistants/SKILL.md +1 -1
  11. package/skills/claude-code/examples/food-ordering/src/helpers.ts +1 -0
  12. package/skills/claude-code/examples/food-ordering/src/views/ordering-flow.tsx +50 -47
  13. package/skills/claude-code/examples/food-ordering/test/ordering-flow.test.ts +3 -0
  14. package/skills/claude-code/executing-noodle-plans/SKILL.md +1 -1
  15. package/skills/claude-code/publishing-mcp-integrations/SKILL.md +1 -1
  16. package/skills/claude-code/references/widgets-and-apps.md +1 -1
  17. package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
  18. package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
  19. package/skills/claude-code/wrapping-existing-applications/SKILL.md +1 -1
  20. package/skills/codex/SKILL.md +1 -1
  21. package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
  22. package/skills/codex/building-mcp-apps/SKILL.md +1 -1
  23. package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
  24. package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
  25. package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
  26. package/skills/codex/designing-mcp-products/SKILL.md +1 -1
  27. package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
  28. package/skills/codex/examples/food-ordering/src/helpers.ts +1 -0
  29. package/skills/codex/examples/food-ordering/src/views/ordering-flow.tsx +50 -47
  30. package/skills/codex/examples/food-ordering/test/ordering-flow.test.ts +3 -0
  31. package/skills/codex/executing-noodle-plans/SKILL.md +1 -1
  32. package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
  33. package/skills/codex/references/widgets-and-apps.md +1 -1
  34. package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
  35. package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
  36. package/skills/codex/wrapping-existing-applications/SKILL.md +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/agent-kit",
3
- "version": "0.62.0",
3
+ "version": "0.62.1",
4
4
  "private": false,
5
5
  "description": "Self-checking, self-updating agent skills for the Noodle Seed CLI. Authored in this repo by @noodle-borg/agent-kit; this is the published, independently-versioned canonical skills artifact the CLI fetches and verifies.",
6
6
  "license": "Apache-2.0",
@@ -3,7 +3,7 @@ name: noodle-seed
3
3
  description: "Use when building, validating, testing, deploying, or operating a local or hosted Noodle Seed MCP server or app authored in TypeScript with the noodle CLI."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:d83543258e5ca5ff -->
6
+ <!-- noodle-skill version:0.62.1 hash:d83543258e5ca5ff -->
7
7
 
8
8
  # Noodle Seed
9
9
 
@@ -3,7 +3,7 @@ name: authoring-mcp-servers
3
3
  description: "Use when creating or extending a headless Noodle Seed MCP server, tool, resource, prompt, or typed model-facing capability."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:0b2fd8c7e43fc69f -->
6
+ <!-- noodle-skill version:0.62.1 hash:0b2fd8c7e43fc69f -->
7
7
 
8
8
  # authoring-mcp-servers
9
9
 
@@ -3,7 +3,7 @@ name: building-mcp-apps
3
3
  description: "Use when a Noodle Seed MCP App, widget, interactive card, visual interaction, or host-visible UI is the primary requested outcome."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:f7fa54992c8d7692 -->
6
+ <!-- noodle-skill version:0.62.1 hash:f7fa54992c8d7692 -->
7
7
 
8
8
  # building-mcp-apps
9
9
 
@@ -3,7 +3,7 @@ name: connecting-apis-to-mcp
3
3
  description: "Use when all four API-evidence inputs exist—and only then: API base URL, authentication scheme, representative safe read, and observed response."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:21bbd3ec441ffd30 -->
6
+ <!-- noodle-skill version:0.62.1 hash:21bbd3ec441ffd30 -->
7
7
 
8
8
  # connecting-apis-to-mcp
9
9
 
@@ -3,7 +3,7 @@ name: debugging-mcp-delivery
3
3
  description: "Use when an existing Noodle Seed MCP project has a concrete validation, runtime, connector, App, host, deployment, or production failure."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:aa715bae12041d7c -->
6
+ <!-- noodle-skill version:0.62.1 hash:aa715bae12041d7c -->
7
7
 
8
8
  # debugging-mcp-delivery
9
9
 
@@ -3,7 +3,7 @@ name: deploying-mcp-services
3
3
  description: "Use when the user explicitly requests a Noodle Seed hosted link, configuration write, deployment, access change, rollback, or connection write."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:93e735b7ffb45df1 -->
6
+ <!-- noodle-skill version:0.62.1 hash:93e735b7ffb45df1 -->
7
7
 
8
8
  # deploying-mcp-services
9
9
 
@@ -3,7 +3,7 @@ name: designing-mcp-products
3
3
  description: "Use when a Noodle Seed MCP product idea needs conversational fit, user benefit, scope, interaction, or evidence design before implementation."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:76cce86729cffbee -->
6
+ <!-- noodle-skill version:0.62.1 hash:76cce86729cffbee -->
7
7
 
8
8
  # designing-mcp-products
9
9
 
@@ -3,7 +3,7 @@ name: embedding-mcp-assistants
3
3
  description: "Use when embedding a Noodle assistant into an existing SaaS or web application with browser, identity, session, and credential boundaries."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:cc54a67f21c0ecdb -->
6
+ <!-- noodle-skill version:0.62.1 hash:cc54a67f21c0ecdb -->
7
7
 
8
8
  # embedding-mcp-assistants
9
9
 
@@ -10,6 +10,7 @@ export {
10
10
  DataList,
11
11
  Feedback,
12
12
  Field,
13
+ Form,
13
14
  HandoffButton,
14
15
  QuantityStepper,
15
16
  ShellNav,
@@ -9,6 +9,7 @@ import {
9
9
  DataList,
10
10
  Feedback,
11
11
  Field,
12
+ Form,
12
13
  HandoffButton,
13
14
  QuantityStepper,
14
15
  ShellNav,
@@ -340,53 +341,55 @@ export default function OrderingFlow() {
340
341
  <div className="nw-body">
341
342
  <ViewStack flow={flow}>
342
343
  <View name="stores">
343
- <div className="nw-field-grid">
344
- <Field className="nw-field" label="Customer">
345
- <input
346
- className="nw-input"
347
- value={customer}
348
- onChange={(event) => setCustomer(event.currentTarget.value)}
349
- />
350
- </Field>
351
- <Field className="nw-field" label="Search">
352
- <input
353
- className="nw-input"
354
- value={query}
355
- placeholder="Noodles"
356
- onChange={(event) => setQuery(event.currentTarget.value)}
357
- />
358
- </Field>
359
- </div>
360
- <ActionBar className="nw-actions">
361
- <SubmitButton
362
- type="button"
363
- className="nw-button nw-button-primary"
364
- pending={searchStores.isPending}
365
- pendingLabel="Searching..."
366
- onClick={() => searchStores.callTool({ query, openOnly: false })}
367
- >
368
- <SearchIcon />
369
- Search stores
370
- </SubmitButton>
371
- <SubmitButton
372
- type="button"
373
- className="nw-button"
374
- pending={readCart.isPending}
375
- pendingLabel="Loading..."
376
- onClick={async () => {
377
- const result = await readCart.callTool({});
378
- const stored = structured<{
379
- readonly value?: CartState;
380
- readonly revision?: number;
381
- }>(result);
382
- if (stored?.value?.lines) setCart(stored.value);
383
- setRevision(stored?.revision ?? revision);
384
- }}
385
- >
386
- <RefreshIcon />
387
- Load cart
388
- </SubmitButton>
389
- </ActionBar>
344
+ <Form onSubmit={() => void searchStores.callTool({ query, openOnly: false })}>
345
+ <div className="nw-field-grid">
346
+ <Field className="nw-field" label="Customer">
347
+ <input
348
+ className="nw-input"
349
+ value={customer}
350
+ onChange={(event) => setCustomer(event.currentTarget.value)}
351
+ />
352
+ </Field>
353
+ <Field className="nw-field" label="Search">
354
+ <input
355
+ className="nw-input"
356
+ value={query}
357
+ placeholder="Noodles"
358
+ onChange={(event) => setQuery(event.currentTarget.value)}
359
+ />
360
+ </Field>
361
+ </div>
362
+ <ActionBar className="nw-actions">
363
+ <SubmitButton
364
+ type="submit"
365
+ className="nw-button nw-button-primary"
366
+ disabled={!ready}
367
+ pending={searchStores.isPending}
368
+ pendingLabel="Searching..."
369
+ >
370
+ <SearchIcon />
371
+ Search stores
372
+ </SubmitButton>
373
+ <SubmitButton
374
+ type="button"
375
+ className="nw-button"
376
+ pending={readCart.isPending}
377
+ pendingLabel="Loading..."
378
+ onClick={async () => {
379
+ const result = await readCart.callTool({});
380
+ const stored = structured<{
381
+ readonly value?: CartState;
382
+ readonly revision?: number;
383
+ }>(result);
384
+ if (stored?.value?.lines) setCart(stored.value);
385
+ setRevision(stored?.revision ?? revision);
386
+ }}
387
+ >
388
+ <RefreshIcon />
389
+ Load cart
390
+ </SubmitButton>
391
+ </ActionBar>
392
+ </Form>
390
393
  <AsyncBoundary
391
394
  state={searchStores}
392
395
  isEmpty={displayedStores.length === 0}
@@ -38,6 +38,7 @@ vi.mock('../src/helpers.js', async () => {
38
38
  Feedback: ({ children, status }: { readonly children?: unknown; readonly status?: string }) =>
39
39
  createElement('div', { 'data-status': status }, children),
40
40
  Field: container,
41
+ Form: ({ children }: { readonly children?: unknown }) => createElement('form', null, children),
41
42
  HandoffButton: button,
42
43
  QuantityStepper: container,
43
44
  ShellNav: container,
@@ -71,6 +72,7 @@ vi.mock('../src/helpers.js', async () => {
71
72
  useUpdateModelContext: () => () => Promise.resolve(),
72
73
  useViewState: <T>(_key: string, initial: T) => [initial, () => undefined] as const,
73
74
  useWidgetLifecycle: () => () => Promise.resolve(),
75
+ useWidgetReady: () => true,
74
76
  View: container,
75
77
  ViewStack: container,
76
78
  };
@@ -183,5 +185,6 @@ describe('food-ordering invoking result states', () => {
183
185
  });
184
186
  expect(text).toContain('Search stores');
185
187
  expect(text).toContain('Harbor Noodles');
188
+ expect(document.querySelector('form')).not.toBeNull();
186
189
  });
187
190
  });
@@ -3,7 +3,7 @@ name: executing-noodle-plans
3
3
  description: "Use when the user asks to execute an approved, decision-complete implementation plan for a Noodle Seed project task by task with test-first changes, review, recovery, and final verification."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:6a9f132ddb79352e -->
6
+ <!-- noodle-skill version:0.62.1 hash:6a9f132ddb79352e -->
7
7
 
8
8
  # Execute a Noodle Seed implementation plan
9
9
 
@@ -3,7 +3,7 @@ name: publishing-mcp-integrations
3
3
  description: "Use when preparing, reviewing, or submitting a Noodle Seed MCP integration to a host or app directory."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:efffbf82007f935d -->
6
+ <!-- noodle-skill version:0.62.1 hash:efffbf82007f935d -->
7
7
 
8
8
  # publishing-mcp-integrations
9
9
 
@@ -48,7 +48,7 @@ Author views as React components. `generateHelpers<ServerDefinition>()` (from `@
48
48
  | `useAppFlow` | Manage named widget views with persisted params and back-stack state: `const flow = useAppFlow({ initialView, views })`. |
49
49
  | `useHandoff` | Open server-created HTTP(S) handoff URLs through the host with status/error state; domain policy still comes from `handoff.allowedDomains`. |
50
50
 
51
- Bind interactive elements to tools (`useCallTool("place_order")`), keep bridge-backed controls disabled until `useWidgetReady()` is true, drive named views with `useAppFlow(...)`, open server-created handoffs with `useHandoff()`, and publish one compact, safe, cohesive snapshot with `useUpdateModelContext()` when `useLayout().supports?.modelContext` is true. Generated form workflows use the portable `<Form onSubmit={...}>` component with a submit button; never add browser-navigation `action`, `method`, or `target` attributes. Existing intrinsic React forms are valid when their `onSubmit` handler calls `preventDefault()` before the explicit tool call and omits those navigation attributes. Standalone actions use an explicit `type="button"` and call the tool from their click handler. In every case the widget calls the standard MCP Apps tool bridge itself — never rely on a host to translate native form submission into a tool call, and never branch on a host name. Every model-context or lifecycle publication replaces the prior snapshot rather than merging fields, so include everything the model should still know. Calling `useWidgetLifecycle("name")` automatically publishes `mounted`, listens for host `cancelled` and `dismissed`, and returns a publisher for author-owned `submitted` or app-specific milestones; `mounted` is not proof that the host presented pixels. Both hooks use the standard MCP Apps model-context channel, not a host-specific API. These updates affect future model context but do not start a model turn. When an explicit user submit/cancel should receive an immediate reply, also call `useSendFollowUpMessage()` from that user action. `data-llm` may remain a DOM inspection hint, but it is not the bidirectional model-state contract. Use `createViewStore("key", initial)` for multi-component widget state such as carts, filters, or drafts. Use the domain-neutral React components from `@noodleseed/one/react` (`AppShell`, `ShellNav`, `ViewStack`, `AsyncBoundary`, `ActionBar`, `Form`, `Field`, `QuantityStepper`, `ChoiceGroup`, `HandoffButton`, and related state components) for rich apps before inventing local shell/control scaffolding. Adapt to the host with `useLayout()` — style for both `theme` values, and keep the inline `displayMode` compact (content fits the space; no internal scrolling). Trigger `useOpenExternal()`, `useHandoff()`, and `useSendFollowUpMessage()` only from explicit user actions. A raw `html` escape hatch exists for self-contained widgets (declarative `data-bind`/`data-action`; no inline `<script>`); raw markup must cancel native form navigation explicitly.
51
+ Bind interactive elements to tools (`useCallTool("place_order")`), keep bridge-backed controls disabled until `useWidgetReady()` is true, drive named views with `useAppFlow(...)`, open server-created handoffs with `useHandoff()`, and publish one compact, safe, cohesive snapshot with `useUpdateModelContext()` when `useLayout().supports?.modelContext` is true. Generated form workflows use the portable `<Form onSubmit={() => void submit()}>` component with a submit button; never use an intrinsic React `<form>` or add browser-navigation `action`, `method`, or `target` attributes. A sandbox can block native activation before React receives `onSubmit`, so `preventDefault()` inside an intrinsic handler is not a portability fix. Standalone actions use an explicit `type="button"` and call the tool from their click handler. In every case the widget calls the standard MCP Apps tool bridge itself — never rely on a host to translate native form submission into a tool call, and never branch on a host name. Every model-context or lifecycle publication replaces the prior snapshot rather than merging fields, so include everything the model should still know. Calling `useWidgetLifecycle("name")` automatically publishes `mounted`, listens for host `cancelled` and `dismissed`, and returns a publisher for author-owned `submitted` or app-specific milestones; `mounted` is not proof that the host presented pixels. Both hooks use the standard MCP Apps model-context channel, not a host-specific API. These updates affect future model context but do not start a model turn. When an explicit user submit/cancel should receive an immediate reply, also call `useSendFollowUpMessage()` from that user action. `data-llm` may remain a DOM inspection hint, but it is not the bidirectional model-state contract. Use `createViewStore("key", initial)` for multi-component widget state such as carts, filters, or drafts. Use the domain-neutral React components from `@noodleseed/one/react` (`AppShell`, `ShellNav`, `ViewStack`, `AsyncBoundary`, `ActionBar`, `Form`, `Field`, `QuantityStepper`, `ChoiceGroup`, `HandoffButton`, and related state components) for rich apps before inventing local shell/control scaffolding. Adapt to the host with `useLayout()` — style for both `theme` values, and keep the inline `displayMode` compact (content fits the space; no internal scrolling). Trigger `useOpenExternal()`, `useHandoff()`, and `useSendFollowUpMessage()` only from explicit user actions. A raw `html` escape hatch exists for self-contained widgets (declarative `data-bind`/`data-action`; no inline `<script>`); use a `data-action` button with `type="button"` instead of native form submission.
52
52
 
53
53
  ## Worked widget recipe
54
54
 
@@ -3,7 +3,7 @@ name: reporting-noodle-feedback
3
3
  description: "Use when a Noodle Seed bug, misleading instruction, missing capability, or concrete product improvement should be proposed to the user."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:0f404109f4845683 -->
6
+ <!-- noodle-skill version:0.62.1 hash:0f404109f4845683 -->
7
7
 
8
8
  # reporting-noodle-feedback
9
9
 
@@ -3,7 +3,7 @@ name: verifying-mcp-delivery
3
3
  description: "Use when proving a Noodle Seed MCP project works at a named compile, local, connector, App, host, deployment, or production evidence level."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:6ef6ef551e26b78e -->
6
+ <!-- noodle-skill version:0.62.1 hash:6ef6ef551e26b78e -->
7
7
 
8
8
  # verifying-mcp-delivery
9
9
 
@@ -3,7 +3,7 @@ name: wrapping-existing-applications
3
3
  description: "Use when an existing application has no stable usable API and needs a read-only, identity-first Noodle Seed integration plan before implementation."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:eccc3c158dcafba8 -->
6
+ <!-- noodle-skill version:0.62.1 hash:eccc3c158dcafba8 -->
7
7
 
8
8
  # wrapping-existing-applications
9
9
 
@@ -3,7 +3,7 @@ name: noodle-seed
3
3
  description: "Use when building, validating, testing, deploying, or operating a local or hosted Noodle Seed MCP server or app authored in TypeScript with the noodle CLI."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:d83543258e5ca5ff -->
6
+ <!-- noodle-skill version:0.62.1 hash:d83543258e5ca5ff -->
7
7
 
8
8
  # Noodle Seed
9
9
 
@@ -3,7 +3,7 @@ name: authoring-mcp-servers
3
3
  description: "Use when creating or extending a headless Noodle Seed MCP server, tool, resource, prompt, or typed model-facing capability."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:0b2fd8c7e43fc69f -->
6
+ <!-- noodle-skill version:0.62.1 hash:0b2fd8c7e43fc69f -->
7
7
 
8
8
  # authoring-mcp-servers
9
9
 
@@ -3,7 +3,7 @@ name: building-mcp-apps
3
3
  description: "Use when a Noodle Seed MCP App, widget, interactive card, visual interaction, or host-visible UI is the primary requested outcome."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:f7fa54992c8d7692 -->
6
+ <!-- noodle-skill version:0.62.1 hash:f7fa54992c8d7692 -->
7
7
 
8
8
  # building-mcp-apps
9
9
 
@@ -3,7 +3,7 @@ name: connecting-apis-to-mcp
3
3
  description: "Use when all four API-evidence inputs exist—and only then: API base URL, authentication scheme, representative safe read, and observed response."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:21bbd3ec441ffd30 -->
6
+ <!-- noodle-skill version:0.62.1 hash:21bbd3ec441ffd30 -->
7
7
 
8
8
  # connecting-apis-to-mcp
9
9
 
@@ -3,7 +3,7 @@ name: debugging-mcp-delivery
3
3
  description: "Use when an existing Noodle Seed MCP project has a concrete validation, runtime, connector, App, host, deployment, or production failure."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:aa715bae12041d7c -->
6
+ <!-- noodle-skill version:0.62.1 hash:aa715bae12041d7c -->
7
7
 
8
8
  # debugging-mcp-delivery
9
9
 
@@ -3,7 +3,7 @@ name: deploying-mcp-services
3
3
  description: "Use when the user explicitly requests a Noodle Seed hosted link, configuration write, deployment, access change, rollback, or connection write."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:93e735b7ffb45df1 -->
6
+ <!-- noodle-skill version:0.62.1 hash:93e735b7ffb45df1 -->
7
7
 
8
8
  # deploying-mcp-services
9
9
 
@@ -3,7 +3,7 @@ name: designing-mcp-products
3
3
  description: "Use when a Noodle Seed MCP product idea needs conversational fit, user benefit, scope, interaction, or evidence design before implementation."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:76cce86729cffbee -->
6
+ <!-- noodle-skill version:0.62.1 hash:76cce86729cffbee -->
7
7
 
8
8
  # designing-mcp-products
9
9
 
@@ -3,7 +3,7 @@ name: embedding-mcp-assistants
3
3
  description: "Use when embedding a Noodle assistant into an existing SaaS or web application with browser, identity, session, and credential boundaries."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:cc54a67f21c0ecdb -->
6
+ <!-- noodle-skill version:0.62.1 hash:cc54a67f21c0ecdb -->
7
7
 
8
8
  # embedding-mcp-assistants
9
9
 
@@ -10,6 +10,7 @@ export {
10
10
  DataList,
11
11
  Feedback,
12
12
  Field,
13
+ Form,
13
14
  HandoffButton,
14
15
  QuantityStepper,
15
16
  ShellNav,
@@ -9,6 +9,7 @@ import {
9
9
  DataList,
10
10
  Feedback,
11
11
  Field,
12
+ Form,
12
13
  HandoffButton,
13
14
  QuantityStepper,
14
15
  ShellNav,
@@ -340,53 +341,55 @@ export default function OrderingFlow() {
340
341
  <div className="nw-body">
341
342
  <ViewStack flow={flow}>
342
343
  <View name="stores">
343
- <div className="nw-field-grid">
344
- <Field className="nw-field" label="Customer">
345
- <input
346
- className="nw-input"
347
- value={customer}
348
- onChange={(event) => setCustomer(event.currentTarget.value)}
349
- />
350
- </Field>
351
- <Field className="nw-field" label="Search">
352
- <input
353
- className="nw-input"
354
- value={query}
355
- placeholder="Noodles"
356
- onChange={(event) => setQuery(event.currentTarget.value)}
357
- />
358
- </Field>
359
- </div>
360
- <ActionBar className="nw-actions">
361
- <SubmitButton
362
- type="button"
363
- className="nw-button nw-button-primary"
364
- pending={searchStores.isPending}
365
- pendingLabel="Searching..."
366
- onClick={() => searchStores.callTool({ query, openOnly: false })}
367
- >
368
- <SearchIcon />
369
- Search stores
370
- </SubmitButton>
371
- <SubmitButton
372
- type="button"
373
- className="nw-button"
374
- pending={readCart.isPending}
375
- pendingLabel="Loading..."
376
- onClick={async () => {
377
- const result = await readCart.callTool({});
378
- const stored = structured<{
379
- readonly value?: CartState;
380
- readonly revision?: number;
381
- }>(result);
382
- if (stored?.value?.lines) setCart(stored.value);
383
- setRevision(stored?.revision ?? revision);
384
- }}
385
- >
386
- <RefreshIcon />
387
- Load cart
388
- </SubmitButton>
389
- </ActionBar>
344
+ <Form onSubmit={() => void searchStores.callTool({ query, openOnly: false })}>
345
+ <div className="nw-field-grid">
346
+ <Field className="nw-field" label="Customer">
347
+ <input
348
+ className="nw-input"
349
+ value={customer}
350
+ onChange={(event) => setCustomer(event.currentTarget.value)}
351
+ />
352
+ </Field>
353
+ <Field className="nw-field" label="Search">
354
+ <input
355
+ className="nw-input"
356
+ value={query}
357
+ placeholder="Noodles"
358
+ onChange={(event) => setQuery(event.currentTarget.value)}
359
+ />
360
+ </Field>
361
+ </div>
362
+ <ActionBar className="nw-actions">
363
+ <SubmitButton
364
+ type="submit"
365
+ className="nw-button nw-button-primary"
366
+ disabled={!ready}
367
+ pending={searchStores.isPending}
368
+ pendingLabel="Searching..."
369
+ >
370
+ <SearchIcon />
371
+ Search stores
372
+ </SubmitButton>
373
+ <SubmitButton
374
+ type="button"
375
+ className="nw-button"
376
+ pending={readCart.isPending}
377
+ pendingLabel="Loading..."
378
+ onClick={async () => {
379
+ const result = await readCart.callTool({});
380
+ const stored = structured<{
381
+ readonly value?: CartState;
382
+ readonly revision?: number;
383
+ }>(result);
384
+ if (stored?.value?.lines) setCart(stored.value);
385
+ setRevision(stored?.revision ?? revision);
386
+ }}
387
+ >
388
+ <RefreshIcon />
389
+ Load cart
390
+ </SubmitButton>
391
+ </ActionBar>
392
+ </Form>
390
393
  <AsyncBoundary
391
394
  state={searchStores}
392
395
  isEmpty={displayedStores.length === 0}
@@ -38,6 +38,7 @@ vi.mock('../src/helpers.js', async () => {
38
38
  Feedback: ({ children, status }: { readonly children?: unknown; readonly status?: string }) =>
39
39
  createElement('div', { 'data-status': status }, children),
40
40
  Field: container,
41
+ Form: ({ children }: { readonly children?: unknown }) => createElement('form', null, children),
41
42
  HandoffButton: button,
42
43
  QuantityStepper: container,
43
44
  ShellNav: container,
@@ -71,6 +72,7 @@ vi.mock('../src/helpers.js', async () => {
71
72
  useUpdateModelContext: () => () => Promise.resolve(),
72
73
  useViewState: <T>(_key: string, initial: T) => [initial, () => undefined] as const,
73
74
  useWidgetLifecycle: () => () => Promise.resolve(),
75
+ useWidgetReady: () => true,
74
76
  View: container,
75
77
  ViewStack: container,
76
78
  };
@@ -183,5 +185,6 @@ describe('food-ordering invoking result states', () => {
183
185
  });
184
186
  expect(text).toContain('Search stores');
185
187
  expect(text).toContain('Harbor Noodles');
188
+ expect(document.querySelector('form')).not.toBeNull();
186
189
  });
187
190
  });
@@ -3,7 +3,7 @@ name: executing-noodle-plans
3
3
  description: "Use when the user asks to execute an approved, decision-complete implementation plan for a Noodle Seed project task by task with test-first changes, review, recovery, and final verification."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:6a9f132ddb79352e -->
6
+ <!-- noodle-skill version:0.62.1 hash:6a9f132ddb79352e -->
7
7
 
8
8
  # Execute a Noodle Seed implementation plan
9
9
 
@@ -3,7 +3,7 @@ name: publishing-mcp-integrations
3
3
  description: "Use when preparing, reviewing, or submitting a Noodle Seed MCP integration to a host or app directory."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:efffbf82007f935d -->
6
+ <!-- noodle-skill version:0.62.1 hash:efffbf82007f935d -->
7
7
 
8
8
  # publishing-mcp-integrations
9
9
 
@@ -48,7 +48,7 @@ Author views as React components. `generateHelpers<ServerDefinition>()` (from `@
48
48
  | `useAppFlow` | Manage named widget views with persisted params and back-stack state: `const flow = useAppFlow({ initialView, views })`. |
49
49
  | `useHandoff` | Open server-created HTTP(S) handoff URLs through the host with status/error state; domain policy still comes from `handoff.allowedDomains`. |
50
50
 
51
- Bind interactive elements to tools (`useCallTool("place_order")`), keep bridge-backed controls disabled until `useWidgetReady()` is true, drive named views with `useAppFlow(...)`, open server-created handoffs with `useHandoff()`, and publish one compact, safe, cohesive snapshot with `useUpdateModelContext()` when `useLayout().supports?.modelContext` is true. Generated form workflows use the portable `<Form onSubmit={...}>` component with a submit button; never add browser-navigation `action`, `method`, or `target` attributes. Existing intrinsic React forms are valid when their `onSubmit` handler calls `preventDefault()` before the explicit tool call and omits those navigation attributes. Standalone actions use an explicit `type="button"` and call the tool from their click handler. In every case the widget calls the standard MCP Apps tool bridge itself — never rely on a host to translate native form submission into a tool call, and never branch on a host name. Every model-context or lifecycle publication replaces the prior snapshot rather than merging fields, so include everything the model should still know. Calling `useWidgetLifecycle("name")` automatically publishes `mounted`, listens for host `cancelled` and `dismissed`, and returns a publisher for author-owned `submitted` or app-specific milestones; `mounted` is not proof that the host presented pixels. Both hooks use the standard MCP Apps model-context channel, not a host-specific API. These updates affect future model context but do not start a model turn. When an explicit user submit/cancel should receive an immediate reply, also call `useSendFollowUpMessage()` from that user action. `data-llm` may remain a DOM inspection hint, but it is not the bidirectional model-state contract. Use `createViewStore("key", initial)` for multi-component widget state such as carts, filters, or drafts. Use the domain-neutral React components from `@noodleseed/one/react` (`AppShell`, `ShellNav`, `ViewStack`, `AsyncBoundary`, `ActionBar`, `Form`, `Field`, `QuantityStepper`, `ChoiceGroup`, `HandoffButton`, and related state components) for rich apps before inventing local shell/control scaffolding. Adapt to the host with `useLayout()` — style for both `theme` values, and keep the inline `displayMode` compact (content fits the space; no internal scrolling). Trigger `useOpenExternal()`, `useHandoff()`, and `useSendFollowUpMessage()` only from explicit user actions. A raw `html` escape hatch exists for self-contained widgets (declarative `data-bind`/`data-action`; no inline `<script>`); raw markup must cancel native form navigation explicitly.
51
+ Bind interactive elements to tools (`useCallTool("place_order")`), keep bridge-backed controls disabled until `useWidgetReady()` is true, drive named views with `useAppFlow(...)`, open server-created handoffs with `useHandoff()`, and publish one compact, safe, cohesive snapshot with `useUpdateModelContext()` when `useLayout().supports?.modelContext` is true. Generated form workflows use the portable `<Form onSubmit={() => void submit()}>` component with a submit button; never use an intrinsic React `<form>` or add browser-navigation `action`, `method`, or `target` attributes. A sandbox can block native activation before React receives `onSubmit`, so `preventDefault()` inside an intrinsic handler is not a portability fix. Standalone actions use an explicit `type="button"` and call the tool from their click handler. In every case the widget calls the standard MCP Apps tool bridge itself — never rely on a host to translate native form submission into a tool call, and never branch on a host name. Every model-context or lifecycle publication replaces the prior snapshot rather than merging fields, so include everything the model should still know. Calling `useWidgetLifecycle("name")` automatically publishes `mounted`, listens for host `cancelled` and `dismissed`, and returns a publisher for author-owned `submitted` or app-specific milestones; `mounted` is not proof that the host presented pixels. Both hooks use the standard MCP Apps model-context channel, not a host-specific API. These updates affect future model context but do not start a model turn. When an explicit user submit/cancel should receive an immediate reply, also call `useSendFollowUpMessage()` from that user action. `data-llm` may remain a DOM inspection hint, but it is not the bidirectional model-state contract. Use `createViewStore("key", initial)` for multi-component widget state such as carts, filters, or drafts. Use the domain-neutral React components from `@noodleseed/one/react` (`AppShell`, `ShellNav`, `ViewStack`, `AsyncBoundary`, `ActionBar`, `Form`, `Field`, `QuantityStepper`, `ChoiceGroup`, `HandoffButton`, and related state components) for rich apps before inventing local shell/control scaffolding. Adapt to the host with `useLayout()` — style for both `theme` values, and keep the inline `displayMode` compact (content fits the space; no internal scrolling). Trigger `useOpenExternal()`, `useHandoff()`, and `useSendFollowUpMessage()` only from explicit user actions. A raw `html` escape hatch exists for self-contained widgets (declarative `data-bind`/`data-action`; no inline `<script>`); use a `data-action` button with `type="button"` instead of native form submission.
52
52
 
53
53
  ## Worked widget recipe
54
54
 
@@ -3,7 +3,7 @@ name: reporting-noodle-feedback
3
3
  description: "Use when a Noodle Seed bug, misleading instruction, missing capability, or concrete product improvement should be proposed to the user."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:0f404109f4845683 -->
6
+ <!-- noodle-skill version:0.62.1 hash:0f404109f4845683 -->
7
7
 
8
8
  # reporting-noodle-feedback
9
9
 
@@ -3,7 +3,7 @@ name: verifying-mcp-delivery
3
3
  description: "Use when proving a Noodle Seed MCP project works at a named compile, local, connector, App, host, deployment, or production evidence level."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:6ef6ef551e26b78e -->
6
+ <!-- noodle-skill version:0.62.1 hash:6ef6ef551e26b78e -->
7
7
 
8
8
  # verifying-mcp-delivery
9
9
 
@@ -3,7 +3,7 @@ name: wrapping-existing-applications
3
3
  description: "Use when an existing application has no stable usable API and needs a read-only, identity-first Noodle Seed integration plan before implementation."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.62.0 hash:eccc3c158dcafba8 -->
6
+ <!-- noodle-skill version:0.62.1 hash:eccc3c158dcafba8 -->
7
7
 
8
8
  # wrapping-existing-applications
9
9