agentleFS
willdadywilldady/platypus/.agents/skills/form-conventions/SKILL.md

form-conventions

Skill130 starsChanged 7 days agoMITView on GitHub

Post-save conventions for Platypus frontend forms — where a save lands the user, what the submit button is called, how a failed delete is shown, and the unsaved-changes guard. Use when adding or changing a form in apps/frontend, its submit button, what happens after it saves, a delete confirm dialog, or a dialog's Cancel button.

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read the shape off apps/frontend/app.

    line 21
  2. Very specific:

    Every form built on useEntityForm is guarded for free: while its values differ from the ones it was seeded or last saved with (isDirty), a …

    line 58
  3. Very specific:

    Every entity form is a real <form> whose onSubmit runs the same save the footer button does: preventDefault(), then the form's submit.

    line 83

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Frontend Form Conventions

Where a save lands

One rule covers every form: a save returns the user to the entity's home, and only leaves the page when the form has no home to return to. Which of the two applies falls out of the entity's shape in the route tree:

ShapeThe form is…On saveExamples
Collection memberthe entity's page, reached from a listgo to listAgents, Blueprints, Skills, Triggers, MCP, Providers, Webhooks, Workspace Context
Entity sub-pagea settings/ child of a page the entity ownsstayBoards
Singletonone per Workspace/Org/account, so there is no liststaySandbox, Organization, Workspace
Dialog or inlinenot a page at allstayMembers, Invitations, Global Context, everything in Chat

Read the shape off apps/frontend/app. settings/agents/[agentId] renders the agent edit form — an agent has no detail page, so leaving the form means returning to settings/agents. A board does have a page, and its form lives at boards/[boardId]/settings, so saving keeps you there.

Staying means mutate() (or router.refresh()) plus a toast. Never router.push to the page you are already on.

Creating always navigates — to the new entity where it has a page of its own, otherwise to the list it now belongs to.

Deleting always navigates away from whatever it destroyed, whatever the shape: to the list for a collection member, to the parent otherwise.

A list is wherever the list actually renders, not a route that looks like one. Triggers navigate to the Workspace home because that is where the trigger list lives; there is no triggers/ page.

When a delete fails

A refused delete is shown inline, in the confirm dialog, which stays open. The spinner stops and the user can read the message, then retry or cancel. This holds for every delete, form or list: use useEntityDelete or useDeleteFlow and pass the error to the dialog. Never toast a failed delete and close the dialog.

The one exception is guidanceOnForbidden, on both hooks, and it is for Shared-resource deletes only. Deleting a Shared resource from a Workspace is refused as forbidden, and the message only says where the resource is managed: guidance, not an error the user can act on in this dialog. With the option set, a forbidden refusal closes the dialog and shows the message as an info toast. Every other refusal still stays inline.

A dialog's Cancel button is variant="outline", never ghost.

Leaving with unsaved changes

Every form built on useEntityForm is guarded for free: while its values differ from the ones it was seeded or last saved with (isDirty), a same-tab link click opens "Discard unsaved changes?" and refresh or tab close shows the browser's prompt. Typing then reverting is clean. A successful save is clean before onSuccess runs, so the post-save redirect goes straight through; a failed save stays dirty.

  • A value the form sets itself — a default once options load, a reset after save — goes through seedFormData, not setFormData, or the untouched form reads as dirty.
  • An edit the form keeps outside formData (a selection, raw JSON text, an avatar file) goes in sideState, or changing it alone never prompts.
  • A deliberate full document load after a write uses hardNavigate, not window.location.href, so the browser's prompt doesn't fire over it.
  • A surface with its own state and no useEntityForm (the Card dialog) calls useUnsavedChangesGuard(isDirty) and routes its own close through the confirmDiscard it returns.
  • A small dialog or inline action built on useEntityForm (member edit, chat rename, issue token) passes guardUnsavedChanges: false: it is not a form the user would lose work in.
  • Programmatic navigation (router.push after save or delete) and browser Back are never held. The App Router cannot block Back.

How the form submits

Every entity form is a real <form> whose onSubmit runs the same save the footer button does: preventDefault(), then the form's submit. The footer passes type="submit" to FormFooterButtons, so Enter in any single-line field saves once.

  • Give every other button inside the form an explicit type="button" — Advanced settings, reveal/copy, Add model, Test connection, Regenerate, and a draft step's Back/Next. An untyped Button defaults to submit.
  • A draft step in the Workspace wizard keeps its own Back/Next buttons and steps through draft.write; the wizard must not save twice.
  • A dialog that renders its own <form> (the Add/Edit model dialog) stops the submit event, because React bubbles it through the portal to the surrounding form.
  • A search field must not submit the form around it.
  • Set noValidate on the form. The shared schemas own validation and their messages, so the browser's step/type bubbles must not block a save.

What the button says

The label names the outcome, so it follows from the destination:

CaseLabel
Create, page-level formSave
Edit that navigates to the listUpdate
Edit saved in placeSave
Adding to a surface you stay onAdd

Update is reserved for the edit-then-leave case — it is the word for a save you are about to be carried away from. Add covers the in-place additions: Add column, Add card.

Let the form heading name the entity and keep the button to the bare verb (Save, not Create board). Prefer the bare verb over Save changes too. A loading state keeps the same verb as the button it replaces, with the repo's ASCII ellipsis: Saving... under Save, Updating... under Update.

Unlike the destination rule, this one is ahead of the code. Most forms today label on {id ? "Update" : "Save"} alone, so a stay-in-place edit can still read Update (Sandbox, Organization, Workspace); #793 brings the existing buttons into line. Write new buttons to the rule above, and leave the stragglers to that issue.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.