Skip to content

docs: stop teaching action="" in the scaffold gallery and skill #1168

Description

@vivek7405

Problem

The scaffold gallery and the optimistic-ui skill reference both teach <form method="post" action="">, and every app webjs create generates carries the shape.

It is not conformant. The HTML spec requires action to be a valid non-empty URL when the attribute is present, so action="" is a conformance error. Nothing breaks in practice, every browser treats it as "submit to the current URL", which is exactly the intent, and that is why it has gone unnoticed. But <form method="post"> with no action at all does the same thing, is conformant, and is one fewer token to explain.

The reason it matters now is that it puts two of our own surfaces in contradiction. #1167 (the #1154 form-action leak guard) documented "omit action rather than writing action="", which the spec treats as a conformance error", which is true, and which a reader would then immediately see violated by the gallery their own webjs create just generated. The claim was softened in that PR rather than shipping a doc that contradicts generated code, so the honest version of the advice is blocked on this cleanup.

Design / approach

Drop the empty attribute wherever it is taught. <form method="post"> posts to the page's own URL identically, so this is a pure deletion with no behaviour change and no migration concern.

Then restore the stronger wording in the docs that #1167 softened, so the guidance states the conformance point instead of quietly preferring one form over another.

One judgement call worth stating: do NOT go looking for action="" in test fixtures and delete it there. packages/core/test/routing/router-client.test.js:2535 uses formaction="" deliberately, as a fixture pinning how the client router resolves an empty formaction. That is testing the behaviour, not teaching the idiom, and it should stay.

Implementation notes (for the implementing agent)

Where to edit. Five teaching sites, found by grep -rn 'action=""' --include=*.ts --include=*.js --include=*.md . | grep -v node_modules:

  • packages/cli/templates/gallery/app/features/forms/page.ts:52 (the forms gallery card's own form)
  • packages/cli/templates/gallery/modules/todo/components/todo-app.ts:100 (the add-todo form)
  • packages/cli/templates/gallery/modules/todo/components/todo-app.ts:112 (the per-item form)
  • .agents/skills/webjs/references/optimistic-ui.md:72 (prose: "Wrap the mutation in a REAL <form method="post" action="">")
  • .agents/skills/webjs/references/optimistic-ui.md:77 (the code sample directly below it)

Then, once the scaffold no longer contradicts it, restore the conformance wording in the two surfaces #1167 softened:

  • .agents/skills/webjs/references/muscle-memory-gotchas.md, the "A function in <form action=${fn}> is refused" entry, RIGHT comment block
  • website/app/docs/troubleshooting/page.ts, the "a function was interpolated into action=" entry, Fix paragraph

Landmines.

  • The agent skill is single-sourced. .agents/skills/webjs/ at the repo root is canonical; packages/cli/lib/create.js around L648 copies the templates/.agents/skills/webjs bundle when it exists (produced at prepack by scripts/sync-scaffold-skill.mjs) and otherwise falls back to the repo-root copy, which is gitignored in the monorepo. So edit the repo-root file ONLY. Do not hand-create a templates/.agents/ copy to "fix" it in both places; that would fork a file the prepack script owns.
  • create.js also runs generated markdown through bunifyProse for --runtime bun (same region, ~L660). It rewrites npm commands, not HTML, so this change is unaffected, but do not be surprised to see the skill files transformed on copy.
  • The gallery files are TypeScript templates emitted as source. They are real files here, not string-generated, so an escaping bug is not a risk for this one; still verify by generating, since that is the only thing that proves what an app actually receives.

Invariants to respect. No behaviour change is intended: a no-JS submit must still post to the page's own URL and reach the page's action export, and the optimistic path in todo-app.ts still depends on @submit + preventDefault(). AGENTS.md invariant 11 applies to the prose edits (the docs-site page is HTML in a template literal, so mind the escaping around ${).

Tests + docs surfaces.

  • Scaffold: test/scaffolds/ covers generation. Verify with a real webjs create app, then webjs check and boot it, per the scaffold-sync requirement; the forms gallery card and the todo example are both directly exercised.
  • Smoke: test/examples/*/smoke/* if the generated app's form routes are covered there.
  • Add an assertion that no scaffold template ships action="", so it cannot creep back. A grep-based test over packages/cli/templates/ is enough and is the counterfactual (it fails today).
  • Docs: the two surfaces listed above, plus .agents/skills/webjs/references/optimistic-ui.md itself.

Process. Invoke webjs-scaffold-sync before editing; it holds the authoritative map of scaffold surfaces and the generate + boot + webjs check verification this needs.

Acceptance criteria

  • No action="" remains in packages/cli/templates/ or in .agents/skills/webjs/references/
  • A freshly generated app (webjs create) contains no action="", passes webjs check, and its forms still submit with JS disabled
  • The todo example's optimistic path still works with JS enabled
  • A test fails if a scaffold template reintroduces action="" (counterfactual: it fails against today's tree)
  • The conformance wording is restored in muscle-memory-gotchas.md and website/app/docs/troubleshooting/page.ts
  • packages/core/test/routing/router-client.test.js:2535 is left alone (it pins behaviour, it does not teach the idiom)

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentation

Type

No type

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions