agentsclimarketplace

Writing javascript

Skill chrisrackstallion/pitchd-rails-cursor-plugin/skills/writing-javascript

Write JavaScript in a Rails app the omakase way — importmap-first delivery, optional bundling (esbuild, Vite) when justified, focused Stimulus controllers under app/javascript/controllers, modern ES module hygiene, accessible DOM updates, safe fetching with CSRF, and a clear boundary with Turbo/HTML. DHH / 37signals style. Use when editing importmap, package.json, application.js, Stimulus controllers, shared JS modules, or choosing bundling; not for Hotwire/Turbo *behaviour in templates* (see writing-hotwire), Tailwind/config/global CSS (see writing-css-tailwind), or ERB markup (see writing-views). For rebalancing an existing Stimulus fleet (merging, splitting, spec coverage), see refactoring-stimulus-controllers.From its SKILL.md

Install
npx -y skills add chrisrackstallion/pitchd-rails-cursor-plugin --skill writing-javascript

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.9 KB, as published. Nobody here has run it

Writing JavaScript (Rails Boundary + Quality)

<objective> Keep JavaScript small, focused, and subservient to the Rails app: importmap and Stimulus are the default; bundling is an intentional exception when npm depth or tooling requires it. One clear pipeline per codebase — no duplicate routers, no client authority over domain state for first-party pages. Beyond the boundary, the code itself must be **modern, modular, accessible, and safe** — ES modules, async/await with real error handling, single-purpose Stimulus controllers, DOM-derived state, CSRF-respecting fetches, and no secrets in the bundle. Styling stays in Tailwind + ERB + minimal Stimulus class toggles — not JS theme systems or CSS-in-JS; **`../writing-css-tailwind`**, **`../writing-views`**. </objective>

When to Use This Skill

UseDefer
config/importmap.rb, package.json, application.js, bundler configTurbo / Stimulus behaviour in templates — ../writing-hotwire
New or edited Stimulus controllers in app/javascript/controllers/Tailwind config and global CSS — ../writing-css-tailwind
Shared JS modules under app/javascript/lib/Helpers and ERB markup — ../writing-views
Choosing importmap vs bundler; adding npm packagesRefactoring an existing Stimulus fleet — ../refactoring-stimulus-controllers
Hand-rolled fetch, CSRF, error handling, accessibility hooksNative mobile / hybrid clients — out of scope

Process

1. Identify the Stack

SignalLikely stack
config/importmap.rb, no build scripts in package.jsonImportmap + Propshaft
package.json with build / esbuild / vite / rollupBundled JS — read references/patterns.md § Bundling
Both presentPick one — see references/patterns.md § One Stack Per App

If the app is on importmap, stay on importmap unless you have a concrete reason to bundle. Bundling has real costs (Node toolchain, Procfile.dev, deploy step) and is justified by npm depth, TypeScript, or workers — not by familiarity.

2. Wire Entrypoints and Registration

app/javascript/application.js is wired into the layout once (via javascript_importmap_tags or the bundler's documented tag). Stimulus loads with eagerLoadControllersFrom("controllers", application) (importmap) or explicit imports (bundler). One discoverable controller tree under app/javascript/controllers/. Details: references/patterns.md.

3. Write Stimulus Controllers Well

A Stimulus controller is good when:

  • It has one responsibility named in the filename (clipboard_controller.js, not ui_controller.js).
  • It uses static targets, static values, static classes, and static outlets — not ad hoc querySelector calls or hard-coded class strings.
  • It derives state from the DOM (targets, values, attributes) — never from a closure variable that morphing will strand.
  • It cleans up everything connect() set up in disconnect().
  • It is <100 lines and reads top-to-bottom. If it grows past that, split it (see ../refactoring-stimulus-controllers).
  • It dispatches CustomEvents (this.dispatch("opened")) for cross-controller signals; outlets when the dependency is structural.

Worked examples and lifecycle details: references/patterns.md § Stimulus.

4. Write Vanilla JS Well

When a Stimulus controller isn't the right home (shared utility, third-party integration, presigned upload helper), put it in app/javascript/lib/<thing>.js:

  • ES modules with named exports. No window.MyApp globals.
  • async / await with try / catch only where you can handle the error; otherwise let it bubble.
  • Strict equality (===), optional chaining (?.), nullish coalescing (??).
  • Pure functions where possible — easier to reason about and to test.
  • No eval, no new Function, no innerHTML = userInput.

5. Fetch Safely

If you must hit an endpoint from JS, default to @rails/request.js for CSRF handling. Send Accept: text/vnd.turbo-stream.html when the response is a stream and let Turbo apply the DOM update. Wrap calls in try / catch, render errors in the DOM, never alert(). Never embed API keys. Patterns: references/patterns.md § Fetching.

6. Respect Accessibility and DOM Safety

Toggle aria-* and hidden alongside CSS classes when state changes. Manage focus on overlay open/close. Prefer textContent; use innerHTML only with server-rendered HTML. Respect prefers-reduced-motion. Patterns: references/patterns.md § Accessibility.

7. Respect the Boundary

Do not add client-side routing, global Redux-style stores, or JSON APIs solely to feed first-party ERB pages. See references/patterns.md § Boundary and ../writing-hotwire for the response hierarchy (redirect → frames → streams).

8. Verify the Behaviour

Stimulus controllers earn at most one canonical system spec per behaviour — see ../writing-tests/references/system-specs.md § Budget. The simplest page that exercises the controller is the canonical home; other usages assume it works. For an existing fleet of controllers without specs, run ../refactoring-stimulus-controllers.

Verification

  • One primary JS delivery path (importmap or documented bundler — not two masters)
  • Stimulus lives under app/javascript/controllers/ and is registered from the app entrypoint
  • Layout uses javascript_importmap_tags or the bundler's documented tag — not ad hoc <script src> for app code
  • No client-side router duplicating Rails routes for same-origin pages
  • No JSON API exists only to feed your own ERB pages
  • Adding npm packages is justified — not "npm install" by default for CRUD
  • Each Stimulus controller has a single responsibility and is under ~100 lines
  • Stimulus controllers use static targets / values / classes / outlets, not query selectors and magic strings
  • connect() is paired with disconnect() for any external listeners, timers, or observers
  • State is derived from the DOM — controllers survive a Turbo morph
  • fetch calls go through @rails/request.js (or include CSRF token) and have try / catch error handling
  • No secrets, API keys, or auth tokens are embedded in client JS
  • No innerHTML with user-controlled input; user input goes through textContent or server-rendered HTML
  • Overlay/modal Stimulus controllers manage focus and toggle aria-* attributes
  • Each Stimulus behaviour has at most one canonical system spec on the simplest page that exercises it

References

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.