codeball 0.2.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a794dad4c470aeca5b9eb3d843f08e31931b2a805b7effa130944cb0c1757d4c
4
+ data.tar.gz: 8c35f43034a1c961982b9404a744525ab41cb98b8d3b883800aae575a4232fc0
5
+ SHA512:
6
+ metadata.gz: e0abbfce874bd19a10e2a095ec2cbc8d8e49c8f0c4059b6b4664fb8617ff6943fd64d5793e14ba783c41b1a8c4d9e2bd3736b0a8279653b79c229c324fcb96f3
7
+ data.tar.gz: e48c6b6449a5b7c0f2a5d606baba454dbed962ab0eade8ecd912cf2803064ac3bc6c5d3116e417aa70a5a021bd5ca7a8dbda4ccf6a468f5c297e8ceb3d30eebb
data/.rubocop.yml ADDED
@@ -0,0 +1,342 @@
1
+ # ===========================================================================
2
+ # RuboCop Configuration
3
+ #
4
+ # Base: Stock RuboCop defaults
5
+ # AI guardrails: rubocop-claude plugin (all Claude/ cops + stricter metrics)
6
+ # Performance: rubocop-performance (with chain-hostile cops disabled)
7
+ #
8
+ # Philosophy: idiomatic Ruby, pipeline-style chaining, strict for AI agents,
9
+ # readable for humans.
10
+ # ===========================================================================
11
+
12
+ plugins:
13
+ - rubocop-claude
14
+ - rubocop-performance
15
+ - rubocop-minitest
16
+ - rubocop-md
17
+ - rubocop-rake
18
+
19
+ AllCops:
20
+ NewCops: enable
21
+ TargetRubyVersion: 3.4.8
22
+
23
+ # ===========================================================================
24
+ # Overrides from stock — personal style preferences
25
+ # ===========================================================================
26
+
27
+ # Double quotes everywhere. One less decision to make.
28
+ #
29
+ # # bad
30
+ # name = 'Alice'
31
+ #
32
+ # # good
33
+ # name = "Alice"
34
+ # greeting = "Hello, #{name}"
35
+ Style/StringLiterals:
36
+ EnforcedStyle: double_quotes
37
+
38
+ Style/StringLiteralsInInterpolation:
39
+ EnforcedStyle: double_quotes
40
+
41
+ # Frozen string literal is transitional cruft. Ruby 3.4 has chilled strings,
42
+ # full default freeze is coming in a future Ruby.
43
+ # EnforcedStyle: never actively removes existing magic comments via autocorrect.
44
+ #
45
+ # # bad — autocorrect will strip this
46
+ # # frozen_string_literal: true
47
+ #
48
+ # class Foo
49
+ # end
50
+ #
51
+ # # good
52
+ # class Foo
53
+ # end
54
+ Style/FrozenStringLiteralComment:
55
+ EnforcedStyle: never
56
+
57
+ # Freezing constants breaks objects that need post-assignment setup (e.g.
58
+ # Zeitwerk loaders). Not worth the churn.
59
+ Style/MutableConstant:
60
+ Enabled: false
61
+
62
+ # Pipeline style. Chaining multi-line blocks is the whole point.
63
+ #
64
+ # # good — this is how we write Ruby
65
+ # users
66
+ # .select { it.active? }
67
+ # .map(&:name)
68
+ # .sort
69
+ #
70
+ # # bad — stock rubocop wants you to break this into temp variables
71
+ # active = users.select { it.active? }
72
+ # names = active.map(&:name)
73
+ # names.sort
74
+ Style/MultilineBlockChain:
75
+ Enabled: false
76
+
77
+ # Block delimiters are a taste call. Pipeline code uses braces for chaining,
78
+ # do/end for side effects. No cop captures this nuance.
79
+ #
80
+ # # good — braces for functional transforms
81
+ # users.map { it.name.downcase }
82
+ #
83
+ # # good — do/end for side effects
84
+ # users.each do |user|
85
+ # send_notification(user)
86
+ # log_activity(user)
87
+ # end
88
+ Style/BlockDelimiters:
89
+ Enabled: false
90
+
91
+ # Write arrays like arrays.
92
+ #
93
+ # # bad
94
+ # colors = %w[red green blue]
95
+ # statuses = %i[active inactive pending]
96
+ #
97
+ # # good
98
+ # colors = ["red", "green", "blue"]
99
+ # statuses = [:active, :inactive, :pending]
100
+ Style/WordArray:
101
+ Enabled: false
102
+
103
+ Style/SymbolArray:
104
+ Enabled: false
105
+
106
+ # Argument indentation: consistent 2-space indent, not aligned to first arg.
107
+ #
108
+ # # bad — renaming the method cascades whitespace changes
109
+ # some_method(arg1,
110
+ # arg2,
111
+ # arg3)
112
+ #
113
+ # # good
114
+ # some_method(
115
+ # arg1,
116
+ # arg2,
117
+ # arg3
118
+ # )
119
+ Layout/FirstArgumentIndentation:
120
+ EnforcedStyle: consistent
121
+
122
+ # Dot-aligned chaining. Dots form a visual column.
123
+ # rubocop-claude sets indented — we override back to aligned.
124
+ #
125
+ # # bad (indented)
126
+ # users.where(active: true)
127
+ # .order(:name)
128
+ # .limit(10)
129
+ #
130
+ # # good (aligned)
131
+ # users.where(active: true)
132
+ # .order(:name)
133
+ # .limit(10)
134
+ Layout/MultilineMethodCallIndentation:
135
+ EnforcedStyle: aligned
136
+
137
+ # ===========================================================================
138
+ # Overrides from rubocop-claude — loosen where pipeline style conflicts
139
+ # ===========================================================================
140
+
141
+ # rubocop-claude sets MaxSafeNavigationChain: 1. That's too tight for
142
+ # chaining code at boundaries (controllers, API responses).
143
+ #
144
+ # # bad — 3+ deep, hiding a nil propagation problem
145
+ # user&.profile&.settings&.theme
146
+ #
147
+ # # good — two-step is normal for optional associations
148
+ # user&.profile&.avatar_url
149
+ #
150
+ # # good — trust internal code, no &. needed
151
+ # user.profile.settings.theme
152
+ Claude/NoOverlyDefensiveCode:
153
+ MaxSafeNavigationChain: 2
154
+
155
+ Style/SafeNavigation:
156
+ MaxChainLength: 2
157
+
158
+ # Allow `return a, b` for tuple-style returns.
159
+ # rubocop-claude sets this; declared here to survive load-order surprises.
160
+ #
161
+ # # bad (stock rubocop flags this)
162
+ # def swap(a, b)
163
+ # return b, a
164
+ # end
165
+ #
166
+ # # good (we allow it)
167
+ # def swap(a, b)
168
+ # return b, a
169
+ # end
170
+ #
171
+ # # still flagged — redundant single return
172
+ # def name
173
+ # return @name
174
+ # end
175
+ Style/RedundantReturn:
176
+ AllowMultipleReturnValues: true
177
+
178
+ # ===========================================================================
179
+ # Overrides from rubocop-performance — disable chain-hostile cops
180
+ # ===========================================================================
181
+
182
+ # ChainArrayAllocation flags idiomatic pipelines for microsecond gains.
183
+ # If we need real throughput we parallelize, not uglify.
184
+ #
185
+ # # "bad" according to this cop — but we write this deliberately
186
+ # users.select(&:active?).map(&:name).sort
187
+ #
188
+ # # "good" according to this cop — mutating, unreadable, not our style
189
+ # users.select!(&:active?)
190
+ # users.map!(&:name)
191
+ # users.sort!
192
+ Performance/ChainArrayAllocation:
193
+ Enabled: false
194
+
195
+ # MapMethodChain wants to collapse chained maps into one block.
196
+ # That's the opposite of pipeline decomposition.
197
+ #
198
+ # # "bad" according to this cop — but each step is atomic and named
199
+ # users
200
+ # .map(&:name)
201
+ # .map(&:downcase)
202
+ #
203
+ # # "good" according to this cop — combines concerns into one block
204
+ # users.map { it.name.downcase }
205
+ Performance/MapMethodChain:
206
+ Enabled: false
207
+
208
+ # ===========================================================================
209
+ # Additional tightening — not set by stock or rubocop-claude
210
+ # ===========================================================================
211
+
212
+ # Short blocks push toward small chained steps instead of fat lambdas.
213
+ # Stock default is 25. rubocop-claude doesn't touch it. We want 8.
214
+ #
215
+ # # bad — too much in one block, decompose into a pipeline
216
+ # users.map { |user|
217
+ # name = user.full_name
218
+ # parts = name.split(" ")
219
+ # first = parts.first
220
+ # last = parts.last
221
+ # domain = user.email.split("@").last
222
+ # "#{first}.#{last}@#{domain}".downcase
223
+ # }
224
+ #
225
+ # # good — each step is clear
226
+ # users
227
+ # .map(&:full_name)
228
+ # .map { it.split(" ") }
229
+ # .map { "#{it.first}.#{it.last}" }
230
+ # .map(&:downcase)
231
+ Metrics/BlockLength:
232
+ Max: 8
233
+ CountAsOne:
234
+ - array
235
+ - hash
236
+ - heredoc
237
+ - method_call
238
+ AllowedMethods:
239
+ - command
240
+ - describe
241
+ - context
242
+ - shared_examples
243
+ - shared_examples_for
244
+ - shared_context
245
+
246
+ # Anonymous forwarding (*, **, &) breaks TruffleRuby, JRuby, and
247
+ # Ruby < 3.2. Named args are explicit and portable.
248
+ #
249
+ # # bad — anonymous forwarding, not portable
250
+ # def process(*, **, &)
251
+ # other_method(*, **, &)
252
+ # end
253
+ #
254
+ # # good — named, works everywhere, readable
255
+ # def process(*args, **kwargs, &block)
256
+ # other_method(*args, **kwargs, &block)
257
+ # end
258
+ Style/ArgumentsForwarding:
259
+ Enabled: false
260
+
261
+ # Explicit begin/rescue/end is clearer than implicit method-body rescue.
262
+ # The begin block scopes what's being rescued. Without it, the rescue
263
+ # looks like it belongs to the method signature.
264
+ #
265
+ # # bad — what exactly is being rescued here?
266
+ # def process
267
+ # logger.info("starting")
268
+ # result = dangerous_call
269
+ # logger.info("done")
270
+ # rescue NetworkError => e
271
+ # retry_later(e)
272
+ # end
273
+ #
274
+ # # good — begin scopes the danger
275
+ # def process
276
+ # logger.info("starting")
277
+ # begin
278
+ # result = dangerous_call
279
+ # rescue NetworkError => e
280
+ # retry_later(e)
281
+ # end
282
+ # logger.info("done")
283
+ # end
284
+ Style/RedundantBegin:
285
+ Enabled: false
286
+
287
+ # Classes get rdoc. Run `rake rdoc` and keep it honest.
288
+ #
289
+ # # bad
290
+ # class UserService
291
+ # def call(user) = process(user)
292
+ # end
293
+ #
294
+ # # good
295
+ # # Handles user lifecycle operations including activation,
296
+ # # deactivation, and profile updates.
297
+ # class UserService
298
+ # def call(user) = process(user)
299
+ # end
300
+ Style/Documentation:
301
+ Enabled: true
302
+ Exclude:
303
+ - "spec/**/*"
304
+ - "test/**/*"
305
+
306
+ # Trailing commas in multiline literals and arguments.
307
+ # Cleaner diffs — adding an element doesn't touch the previous line.
308
+ # Also saves keystrokes when appending.
309
+ #
310
+ # # bad
311
+ # method_call(
312
+ # arg1,
313
+ # arg2
314
+ # )
315
+ #
316
+ # # good
317
+ # method_call(
318
+ # arg1,
319
+ # arg2,
320
+ # )
321
+ #
322
+ # # bad
323
+ # hash = {
324
+ # name: "Alice",
325
+ # age: 30
326
+ # }
327
+ #
328
+ # # good
329
+ # hash = {
330
+ # name: "Alice",
331
+ # age: 30,
332
+ # }
333
+ Style/TrailingCommaInArrayLiteral:
334
+ EnforcedStyleForMultiline: comma
335
+
336
+ Style/TrailingCommaInHashLiteral:
337
+ EnforcedStyleForMultiline: comma
338
+
339
+ Style/TrailingCommaInArguments:
340
+ EnforcedStyleForMultiline: comma
341
+
342
+
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 4.0.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 David Gillis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # Codeball
2
+
3
+ Bidirectional file bundler for clipboard-friendly LLM workflows
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add codeball
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install codeball
14
+
15
+ ## Usage
16
+
17
+ TODO: Write usage instructions here.
18
+
19
+ ## Development
20
+
21
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
22
+
23
+ ## License
24
+
25
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require "bundler/gem_tasks"
2
+ require "minitest/test_task"
3
+ require "rspec/core/rake_task"
4
+ require "rubocop/rake_task"
5
+ require "gempilot/version_task"
6
+
7
+ Gempilot::VersionTask.new
8
+ Minitest::TestTask.create
9
+ RSpec::Core::RakeTask.new(:spec)
10
+ RuboCop::RakeTask.new
11
+
12
+ namespace :zeitwerk do
13
+ desc "Verify all files follow Zeitwerk naming conventions"
14
+ task :validate do
15
+ ruby "-e", <<~RUBY
16
+ require 'codeball'
17
+ Codeball::LOADER.eager_load(force: true)
18
+ puts 'Zeitwerk: All files loaded successfully.'
19
+ RUBY
20
+ end
21
+ end
22
+
23
+ task default: [:test, :spec, :rubocop]
data/data/wont_pack.md ADDED
@@ -0,0 +1,177 @@
1
+ ---
2
+ name: turbo-for-rails
3
+ description: >
4
+ Use when writing Rails frontend code with Hotwire (Turbo Drive, Turbo Frames, Turbo Streams),
5
+ Stimulus controllers, React components in Rails, ActionCable real-time features, or configuring
6
+ esbuild/TypeScript/CSS bundling for Rails. Also use when writing Cypress tests for Rails apps.
7
+ Trigger when you see turbo_frame_tag, turbo_stream, data-controller, data-action, data-target,
8
+ stimulus-rails, @hotwired/turbo-rails, createRoot with turbo:load, or Rails + React integration.
9
+ ---
10
+
11
+ # Turbo for Rails (Hotwire + React)
12
+
13
+ Hotwire (Turbo + Stimulus) handles 80% of interactivity without writing JavaScript. React is for the 20% needing rich client-side state. Both coexist in one Rails app.
14
+
15
+ ## Quick Reference
16
+
17
+ | Task | Hotwire approach | React approach |
18
+ |---|---|---|
19
+ | Scoped page update | Turbo Frame (`turbo_frame_tag`) | N/A |
20
+ | Multi-region update | Turbo Stream (`.turbo_stream.erb`) | `useState`/`useReducer` + re-render |
21
+ | Toggle/show/hide | Stimulus controller + CSS class | `useState` + conditional render |
22
+ | Real-time push | `turbo_stream_from` + ActionCable | ActionCable subscription + `dispatch` |
23
+ | Form submission | Standard Rails form (Turbo intercepts) | `fetch` with CSRF token |
24
+ | Complex client state | Not ideal -- use React | `useReducer` or Redux Toolkit |
25
+
26
+ ## Turbo Essentials
27
+
28
+ **Drive** -- Always on. Replaces `<body>` on navigation. Use `turbo:load` instead of `DOMContentLoaded`. Disable per-element with `data-turbo="false"`.
29
+
30
+ **Frames** -- Scoped navigation. One frame updated per response. IDs must match between display and form partials.
31
+ ```erb
32
+ <%= turbo_frame_tag(dom_id(concert)) do %>
33
+ <%= link_to "Edit", edit_concert_path(concert) %>
34
+ <% end %>
35
+ ```
36
+ Key options: `src:` (lazy-load), `loading: "lazy"` (defer until visible), `target: "_top"` (break out).
37
+
38
+ **Streams** -- Multi-region updates. Actions: `append`, `prepend`, `replace`, `update`, `remove`, `before`, `after`. Use `target` (single ID) or `targets` (CSS selector).
39
+ ```erb
40
+ <%# app/views/favorites/create.turbo_stream.erb %>
41
+ <%= turbo_stream.append("list", @favorite) %>
42
+ <%= turbo_stream.remove(dom_id(@old)) %>
43
+ <%= turbo_stream.update("count", plain: count) %>
44
+ ```
45
+ Controller: `format.turbo_stream` renders `<action>.turbo_stream.erb` without layout.
46
+
47
+ **Frames vs Streams**: Frame = one element, must be `<turbo-frame>`. Stream = any number of elements, any DOM ID, richer actions.
48
+
49
+ **Critical gotchas**: Use `requestSubmit()` not `submit()`. Lazy-load response must omit `src` (infinite loop). `button_to` needs `form: {data: {"turbo-frame": "id"}}`. When ActionCable broadcasts AND controller returns stream, use `format.turbo_stream { head(:ok) }` to prevent double updates.
50
+
51
+ See references/turbo.md for inline edit pattern, all ERB helpers, and complete gotcha list.
52
+
53
+ ## Stimulus Essentials
54
+
55
+ File: `app/javascript/controllers/<name>_controller.ts`. HTML: `data-controller="<name>"`.
56
+
57
+ ```ts
58
+ import { Controller } from "@hotwired/stimulus"
59
+ export default class MyController extends Controller {
60
+ static targets = ["output"]
61
+ static values = { count: Number }
62
+ outputTarget: HTMLElement // Must declare for TypeScript
63
+ countValue: number // lowercase type, uppercase in static
64
+
65
+ toggle(): void { this.countValue += 1 }
66
+ countValueChanged(): void { this.outputTarget.innerText = `${this.countValue}` }
67
+ }
68
+ ```
69
+
70
+ **Action format**: `data-action="event->controller#method"` (e.g., `click->css#toggle`).
71
+ **Targets**: `data-<controller>-target="name"` -- generates `nameTarget`, `nameTargets`, `hasNameTarget`.
72
+ **Values**: `data-<controller>-<value>-value="x"` -- generates getter/setter + `<value>Changed` callback.
73
+ **Classes**: `data-<controller>-<token>-class="hidden"` -- decouples CSS from JS.
74
+
75
+ **Key rules**: DOM is the state store. `<value>Changed` fires on connect (no separate `connect()` needed). Multiple controllers on one element: `data-controller="css text"`. Chained actions execute in order. After creating `.ts` files, run `bin/rails stimulus:manifest:update`.
76
+
77
+ **Generic reusable controllers** (configure entirely in HTML): CSS toggle, text toggle, CSS flip, sort (MutationObserver). See references/stimulus.md for full implementations.
78
+
79
+ See references/stimulus.md for lifecycle callbacks, params, cross-controller communication, debounce, and all common mistakes.
80
+
81
+ ## React in Rails Essentials
82
+
83
+ Components live in `app/javascript/components/`. Mount on `turbo:load`:
84
+ ```tsx
85
+ document.addEventListener("turbo:load", () => {
86
+ const el = document.getElementById("react-element")
87
+ if (el) createRoot(el).render(<App {...parseDataAttrs(el.dataset)} />)
88
+ })
89
+ ```
90
+ Import entry point in `app/javascript/application.js`. Pass server data via `data-*` attributes on the mount `<div>`.
91
+
92
+ **State**: `useState` for simple, `useReducer` for complex (discriminated union actions), Redux Toolkit for app-wide. Reducers must be synchronous -- async in `useEffect` or thunks.
93
+
94
+ **CSRF**: All non-GET `fetch` calls need `X-CSRF-Token` from `document.querySelector("[name='csrf-token']")`.
95
+
96
+ **useEffect rules**: Cannot be async (wrap inner fn). `[]` = mount only. Return cleanup fn for intervals/subscriptions. Never omit dependency array if effect updates state.
97
+
98
+ **Immutable updates**: `setSeatStatuses(prev.map(...))` -- never mutate in place.
99
+
100
+ See references/react.md for useReducer/Redux patterns, styled-components, Context API, and complete gotcha list.
101
+
102
+ ## ActionCable / Real-Time
103
+
104
+ **Turbo Streams over ActionCable** (zero JS):
105
+ ```erb
106
+ <%= turbo_stream_from(current_user, :favorites) %>
107
+ ```
108
+ ```ruby
109
+ # Model callback (prefer _later_ variants)
110
+ after_create_commit -> { broadcast_append_later_to(user, :favorites, target: "list") }
111
+ after_destroy_commit -> { broadcast_remove_to(user, :favorites) }
112
+ ```
113
+ For multi-region broadcasts, use `Turbo::StreamsChannel.broadcast_stream_to` with `content: ApplicationController.render(...)`. Partials must use locals (no `current_user` -- runs outside request cycle).
114
+
115
+ **Custom channels** (Stimulus or React): Guard against double-subscribe (`if (this.subscription) return`). React subscriptions at module level, not inside `useEffect`. Non-serializable objects (subscriptions) stay outside Redux.
116
+
117
+ **Signed streams for React**: Embed `Turbo::StreamsChannel.signed_stream_name(...)` in a data attribute; subscribe with `channel: "Turbo::StreamsChannel"` + `"signed-stream-name"`.
118
+
119
+ See references/real-time.md for bidirectional channels, Redux thunk integration, and broadcast patterns.
120
+
121
+ ## Setup Quick Reference
122
+
123
+ ```bash
124
+ # New app (book defaults)
125
+ bundle exec rails new . -a propshaft -j esbuild --database postgresql --skip-test --css tailwind
126
+
127
+ # Key packages
128
+ yarn add react react-dom @types/react @types/react-dom
129
+ yarn add @rails/actioncable @types/rails__actioncable
130
+ yarn add --dev typescript tsc-watch cypress
131
+
132
+ # TypeScript dev loop (Procfile.dev)
133
+ js: yarn dev # tsc-watch -> esbuild on success
134
+ css: yarn build:css --watch
135
+ ```
136
+
137
+ **tsconfig.json**: Set `"jsx": "react"`, `"noEmit": true` (esbuild transpiles), `"allowSyntheticDefaultImports": true` (Redux).
138
+
139
+ **Tailwind content paths** -- must include `.turbo_stream.erb` and `.tsx`:
140
+ ```js
141
+ content: ["./app/views/**/*.(html|turbostream).erb", "./app/javascript/**/*.(js|ts|tsx)"]
142
+ ```
143
+
144
+ **esbuild + Tailwind CSS conflict**: If importing a CSS package (e.g., animate.css), rename Tailwind output to `tailwind.css` and update `stylesheet_link_tag`.
145
+
146
+ See references/setup-and-bundling.md for esbuild flags, import maps migration, TypeScript types, and full package.json.
147
+
148
+ ## Testing (Cypress)
149
+
150
+ ```bash
151
+ yarn add --dev cypress eslint-plugin-cypress
152
+ # Gemfile: gem "cypress-rails", gem "dotenv-rails"
153
+ rails cypress:open # interactive rails cypress:run # headless
154
+ ```
155
+
156
+ ```js
157
+ beforeEach(() => {
158
+ cy.request("/cypress_rails_reset_state")
159
+ cy.request("POST", "/test/log_in_user")
160
+ cy.visit("/concerts/last")
161
+ })
162
+ cy.get("[data-cy=submit]").click()
163
+ cy.get("[data-cy=list]").find("article").should("have.lengthOf", 1)
164
+ ```
165
+
166
+ Use `data-cy` attributes for stable selectors. Use test-only controllers for login/setup via `cy.request`. Only one test should walk through the real login form.
167
+
168
+ See references/testing.md for full Cypress command reference, seed patterns, and debugging techniques.
169
+
170
+ ## References
171
+
172
+ - `references/turbo.md` -- Turbo Drive/Frames/Streams, ERB helpers, inline edit pattern, all gotchas
173
+ - `references/stimulus.md` -- Controller structure, actions/targets/values/classes, generic controllers, TypeScript
174
+ - `references/react.md` -- Mounting, hooks, Redux Toolkit, styled-components, CSRF, ActionCable integration
175
+ - `references/real-time.md` -- ActionCable setup, Turbo Stream broadcasts, custom channels, signed streams
176
+ - `references/setup-and-bundling.md` -- rails new, esbuild, TypeScript, Tailwind, Propshaft, import maps
177
+ - `references/testing.md` -- Cypress setup, commands, seed data, test controllers, debugging
data/exe/codeball ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/codeball"
4
+
5
+ Codeball::CLI.start
data/issues.rec ADDED
@@ -0,0 +1,61 @@
1
+ %rec: Issue
2
+ %key: Id
3
+ %typedef: text_t regexp /^.*$/
4
+ %typedef: Status_t enum open in_progress closed
5
+ %type: Id uuid
6
+ %type: Title line
7
+ %type: Description text_t
8
+ %type: Status Status_t
9
+ %type: Updated date
10
+ %auto: Id Updated
11
+ %mandatory: Title Description Status
12
+
13
+ Id: CD8C2A41-A996-4A20-9E80-762201592415
14
+ Updated: Fri, 20 Mar 2026 22:56:44 -0400
15
+ Title: "codeball pack" fails to pack certain files
16
+ Description: When running "codeball pack" on certain files, like data/wont_pack.md, the command:
17
+ + 1. fails silently with no error
18
+ + 2. does not log anything to stdout or stderr
19
+ + 3. exits with error code 0
20
+ +
21
+ + This is very problematic, as user has no way of knowing why pack failed, or even if it failed. This issue was discovered after realizing the pack was missing a bunch of files, long after the fact. Catastrophic issue, to say the least.
22
+ Status: closed
23
+
24
+ Id: 439180B4-31A8-4090-860A-125CB517C111
25
+ Updated: Fri, 27 Mar 2026 21:34:19 -0400
26
+ Title: Version number should appear on --version
27
+ Description: Version number is not appearing, instead showing:
28
+ + codeball --version
29
+ + codeball: version unknown
30
+ +
31
+ + An idiomatic solution should lean on CommandKit's version number feature
32
+ Status: closed
33
+
34
+ Id: 631BE27A-2A48-11F1-93E9-FE6CB9572C2D
35
+ Updated: Fri, 27 Mar 2026 21:49:41 -0400
36
+ Title: Fix all rubocop issues
37
+ Description: Many violations are present that claude code is responsible for. They need to be addressed, and no rubocop configuration should be edited unless it would be unreasonable to work around the rule
38
+ Status: closed
39
+
40
+ Id: f2ca5c36-31c2-11f1-bf73-fa9e1a133f8e
41
+ Updated: Mon, 06 Apr 2026 14:14:39 +0000
42
+ Title: Test suite writes to /tmp via Dir.mktmpdir
43
+ Description: Dir.mktmpdir uses Dir.tmpdir to resolve the parent directory. Dir.tmpdir checks in order (per /usr/share/ruby/tmpdir.rb lines 130-135): ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], Etc.systmpdir, /tmp, then current directory. When none of the env vars are set, it falls back to /tmp. This violates the CLAUDE.md hard rule: NEVER write to /tmp. Affected files: spec/spec_helper.rb (CLIHelper#tmp_dir), spec/codeball/destination_spec.rb, and test/entry_test.rb. All calls have after/teardown cleanup so /tmp is not leaked permanently -- the issue is that writes happen to /tmp at all. Fix: set ENV['TMPDIR'] to a project-local directory in spec_helper.rb and test_helper.rb, or pass an explicit second argument to Dir.mktmpdir.
44
+ Status: closed
45
+
46
+ Id: 2F23ED7C-3499-11F1-8D00-FE6CB9572C2F
47
+ Updated: Fri, 10 Apr 2026 00:53:15 -0400
48
+ Title: `codeball pack` should default to packing all files in dir
49
+ Description: Right now codeball pack does not pack anything when called without args. One constantly has to write the following:
50
+ + ```zsh
51
+ + $ codeball pack **/*(.DN)
52
+ + ```
53
+ +
54
+ + Given that packing all files in repo or workdir is so common, this should be the default
55
+ Status: open
56
+
57
+ Id: A5290DA8-349A-11F1-BE24-FE6CB9572C2F
58
+ Updated: Fri, 10 Apr 2026 01:03:42 -0400
59
+ Title: Add option to ignore warnings
60
+ Description: Add optarg to ignore warnings, and produce exit code 0 if warnings but no errors occur
61
+ Status: open