Skip to content
This repository was archived by the owner on Jul 27, 2025. It is now read-only.

Commit 85f825e

Browse files
committed
Merge remote-tracking branch 'origin/main' into bugfix/ui-pwa-ios
2 parents 8cb6d81 + fcf14f5 commit 85f825e

File tree

98 files changed

+3948
-768
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+3948
-768
lines changed

.cursor/rules/project-conventions.mdc

Lines changed: 1 addition & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -66,54 +66,7 @@ All code should maximize readability and simplicity.
6666
- Example 1: be mindful of loading large data payloads in global layouts
6767
- Example 2: Avoid N+1 queries
6868

69-
### Convention 5: Use Minitest + Fixtures for testing, minimize fixtures
70-
71-
Due to the open-source nature of this project, we have chosen Minitest + Fixtures for testing to maximize familiarity and predictability.
72-
73-
- Always use Minitest and fixtures for testing.
74-
- Keep fixtures to a minimum. Most models should have 2-3 fixtures maximum that represent the "base cases" for that model. "Edge cases" should be created on the fly, within the context of the test which it is needed.
75-
- For tests that require a large number of fixture records to be created, use Rails helpers such as [entries_test_helper.rb](mdc:test/support/entries_test_helper.rb) to act as a "factory" for creating these. For a great example of this, check out [forward_calculator_test.rb](mdc:test/models/account/balance/forward_calculator_test.rb)
76-
- Take a minimal approach to testing—only test the absolutely critical code paths that will significantly increase developer confidence
77-
78-
#### Convention 5a: Write minimal, effective tests
79-
80-
- Use system tests sparingly as they increase the time to complete the test suite
81-
- Only write tests for critical and important code paths
82-
- Write tests as you go, when required
83-
- Take a practical approach to testing. Tests are effective when their presence _significantly increases confidence in the codebase_.
84-
85-
Below are examples of necessary vs. unnecessary tests:
86-
87-
```rb
88-
# GOOD!!
89-
# Necessary test - in this case, we're testing critical domain business logic
90-
test "syncs balances" do
91-
Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once
92-
93-
@account.expects(:start_date).returns(2.days.ago.to_date)
94-
95-
Balance::ForwardCalculator.any_instance.expects(:calculate).returns(
96-
[
97-
Balance.new(date: 1.day.ago.to_date, balance: 1000, cash_balance: 1000, currency: "USD"),
98-
Balance.new(date: Date.current, balance: 1000, cash_balance: 1000, currency: "USD")
99-
]
100-
)
101-
102-
assert_difference "@account.balances.count", 2 do
103-
Balance::Syncer.new(@account, strategy: :forward).sync_balances
104-
end
105-
end
106-
107-
# BAD!!
108-
# Unnecessary test - in this case, this is simply testing ActiveRecord's functionality
109-
test "saves balance" do
110-
balance_record = Balance.new(balance: 100, currency: "USD")
111-
112-
assert balance_record.save
113-
end
114-
```
115-
116-
### Convention 6: Use ActiveRecord for complex validations, DB for simple ones, keep business logic out of DB
69+
### Convention 5: Use ActiveRecord for complex validations, DB for simple ones, keep business logic out of DB
11770

11871
- Enforce `null` checks, unique indexes, and other simple validations in the DB
11972
- ActiveRecord validations _may_ mirror the DB level ones, but not 100% necessary. These are for convenience when error handling in forms. Always prefer client-side form validation when possible.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: false
5+
---
6+
This rule describes how to write Stimulus controllers.
7+
8+
- **Use declarative actions, not imperative event listeners**
9+
- Instead of assigning a Stimulus target and binding it to an event listener in the initializer, always write Controllers + ERB views declaratively by using Stimulus actions in ERB to call methods in the Stimulus JS controller. Below are good vs. bad code.
10+
11+
BAD code:
12+
13+
```js
14+
// BAD!!!! DO NOT DO THIS!!
15+
// Imperative - controller does all the work
16+
export default class extends Controller {
17+
static targets = ["button", "content"]
18+
19+
connect() {
20+
this.buttonTarget.addEventListener("click", this.toggle.bind(this))
21+
}
22+
23+
toggle() {
24+
this.contentTarget.classList.toggle("hidden")
25+
this.buttonTarget.textContent = this.contentTarget.classList.contains("hidden") ? "Show" : "Hide"
26+
}
27+
}
28+
```
29+
30+
GOOD code:
31+
32+
```erb
33+
<!-- Declarative - HTML declares what happens -->
34+
35+
<div data-controller="toggle">
36+
<button data-action="click->toggle#toggle" data-toggle-target="button">Show</button>
37+
<div data-toggle-target="content" class="hidden">Hello World!</div>
38+
</div>
39+
```
40+
41+
```js
42+
// Declarative - controller just responds
43+
export default class extends Controller {
44+
static targets = ["button", "content"]
45+
46+
toggle() {
47+
this.contentTarget.classList.toggle("hidden")
48+
this.buttonTarget.textContent = this.contentTarget.classList.contains("hidden") ? "Show" : "Hide"
49+
}
50+
}
51+
```
52+
53+
- **Keep Stimulus controllers lightweight and simple**
54+
- Always aim for less than 7 controller targets. Any more is a sign of too much complexity.
55+
- Use private methods and expose a clear public API
56+
57+
- **Keep Stimulus controllers focused on what they do best**
58+
- Domain logic does NOT belong in a Stimulus controller
59+
- Stimulus controllers should aim for a single responsibility, or a group of highly related responsibilities
60+
- Make good use of Stimulus's callbacks, actions, targets, values, and classes
61+
62+
- **Component controllers should not be used outside the component**
63+
- If a Stimulus controller is in the app/components directory, it should only be used in its component view. It should not be used anywhere in app/views.
64+

.cursor/rules/testing.mdc

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
description:
3+
globs: test/**
4+
alwaysApply: false
5+
---
6+
Use this rule to learn how to write tests for the Maybe codebase.
7+
8+
Due to the open-source nature of this project, we have chosen Minitest + Fixtures for testing to maximize familiarity and predictability.
9+
10+
- **General testing rules**
11+
- Always use Minitest and fixtures for testing, NEVER rspec or factories
12+
- Keep fixtures to a minimum. Most models should have 2-3 fixtures maximum that represent the "base cases" for that model. "Edge cases" should be created on the fly, within the context of the test which it is needed.
13+
- For tests that require a large number of fixture records to be created, use Rails helpers to help create the records needed for the test, then inline the creation. For example, [entries_test_helper.rb](mdc:test/support/entries_test_helper.rb) provides helpers to easily do this.
14+
15+
- **Write minimal, effective tests**
16+
- Use system tests sparingly as they increase the time to complete the test suite
17+
- Only write tests for critical and important code paths
18+
- Write tests as you go, when required
19+
- Take a practical approach to testing. Tests are effective when their presence _significantly increases confidence in the codebase_.
20+
21+
Below are examples of necessary vs. unnecessary tests:
22+
23+
```rb
24+
# GOOD!!
25+
# Necessary test - in this case, we're testing critical domain business logic
26+
test "syncs balances" do
27+
Holding::Syncer.any_instance.expects(:sync_holdings).returns([]).once
28+
29+
@account.expects(:start_date).returns(2.days.ago.to_date)
30+
31+
Balance::ForwardCalculator.any_instance.expects(:calculate).returns(
32+
[
33+
Balance.new(date: 1.day.ago.to_date, balance: 1000, cash_balance: 1000, currency: "USD"),
34+
Balance.new(date: Date.current, balance: 1000, cash_balance: 1000, currency: "USD")
35+
]
36+
)
37+
38+
assert_difference "@account.balances.count", 2 do
39+
Balance::Syncer.new(@account, strategy: :forward).sync_balances
40+
end
41+
end
42+
43+
# BAD!!
44+
# Unnecessary test - in this case, this is simply testing ActiveRecord's functionality
45+
test "saves balance" do
46+
balance_record = Balance.new(balance: 100, currency: "USD")
47+
48+
assert balance_record.save
49+
end
50+
```
51+
52+
- **Test boundaries correctly**
53+
- Distinguish between commands and query methods. Test output of query methods; test that commands were called with the correct params. See an example below:
54+
55+
```rb
56+
class ExampleClass
57+
def do_something
58+
result = 2 + 2
59+
60+
CustomEventProcessor.process_result(result)
61+
62+
result
63+
end
64+
end
65+
66+
class ExampleClass < ActiveSupport::TestCase
67+
test "boundaries are tested correctly" do
68+
result = ExampleClass.new.do_something
69+
70+
# GOOD - we're only testing that the command was received, not internal implementation details
71+
# The actual tests for CustomEventProcessor belong in a different test suite!
72+
CustomEventProcessor.expects(:process_result).with(4).once
73+
74+
# GOOD - we're testing the implementation of ExampleClass inside its own test suite
75+
assert_equal 4, result
76+
end
77+
end
78+
```
79+
80+
- Never test the implementation details of one class in another classes test suite
81+
82+
- **Stubs and mocks**
83+
- Use `mocha` gem
84+
- Always prefer `OpenStruct` when creating mock instances, or in complex cases, a mock class
85+
- Only mock what's necessary. If you're not testing return values, don't mock a return value.
86+
87+

.cursor/rules/view_conventions.mdc

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
description:
3+
globs: app/views/**,app/javascript/**,app/components/**/*.js
4+
alwaysApply: false
5+
---
6+
Use this rule to learn how to write ERB views, partials, and Stimulus controllers should be incorporated into them.
7+
8+
- **Component vs. Partial Decision Making**
9+
- **Use ViewComponents when:**
10+
- Element has complex logic or styling patterns
11+
- Element will be reused across multiple views/contexts
12+
- Element needs structured styling with variants/sizes (like buttons, badges)
13+
- Element requires interactive behavior or Stimulus controllers
14+
- Element has configurable slots or complex APIs
15+
- Element needs accessibility features or ARIA support
16+
17+
- **Use Partials when:**
18+
- Element is primarily static HTML with minimal logic
19+
- Element is used in only one or few specific contexts
20+
- Element is simple template content (like CTAs, static sections)
21+
- Element doesn't need variants, sizes, or complex configuration
22+
- Element is more about content organization than reusable functionality
23+
24+
- **Prefer components over partials**
25+
- If there is a component available for the use case in app/components, use it
26+
- If there is no component, look for a partial
27+
- If there is no partial, decide between component or partial based on the criteria above
28+
29+
- **Examples of Component vs. Partial Usage**
30+
```erb
31+
<%# Component: Complex, reusable with variants and interactivity %>
32+
<%= render DialogComponent.new(variant: :drawer) do |dialog| %>
33+
<% dialog.with_header(title: "Account Settings") %>
34+
<% dialog.with_body { "Dialog content here" } %>
35+
<% end %>
36+
37+
<%# Component: Interactive with complex styling options %>
38+
<%= render ButtonComponent.new(text: "Save Changes", variant: "primary", confirm: "Are you sure?") %>
39+
40+
<%# Component: Reusable with variants %>
41+
<%= render FilledIconComponent.new(icon: "credit-card", variant: :surface) %>
42+
43+
<%# Partial: Static template content %>
44+
<%= render "shared/logo" %>
45+
46+
<%# Partial: Simple, context-specific content with basic styling %>
47+
<%= render "shared/trend_change", trend: @account.trend, comparison_label: "vs last month" %>
48+
49+
<%# Partial: Simple divider/utility %>
50+
<%= render "shared/ruler", classes: "my-4" %>
51+
52+
<%# Partial: Simple form utility %>
53+
<%= render "shared/form_errors", model: @account %>
54+
```
55+
56+
- **Keep domain logic out of the views**
57+
```erb
58+
<%# BAD!!! %>
59+
60+
<%# This belongs in the component file, not the template file! %>
61+
<% button_classes = { class: "bg-blue-500 hover:bg-blue-600" } %>
62+
63+
<%= tag.button class: button_classes do %>
64+
Save Account
65+
<% end %>
66+
67+
<%# GOOD! %>
68+
69+
<%= tag.button class: computed_button_classes do %>
70+
Save Account
71+
<% end %>
72+
```
73+
74+
- **Stimulus Integration in Views**
75+
- Always use the **declarative approach** when integrating Stimulus controllers
76+
- The ERB template should declare what happens, the Stimulus controller should respond
77+
- Refer to [stimulus_conventions.mdc](mdc:.cursor/rules/stimulus_conventions.mdc) to learn how to incorporate them into
78+
79+
GOOD Stimulus controller integration into views:
80+
81+
```erb
82+
<!-- Declarative - HTML declares what happens -->
83+
84+
<div data-controller="toggle">
85+
<button data-action="click->toggle#toggle" data-toggle-target="button">Show</button>
86+
<div data-toggle-target="content" class="hidden">Hello World!</div>
87+
</div>
88+
```
89+
90+
- **Stimulus Controller Placement Guidelines**
91+
- **Component controllers** (in `app/components/`) should only be used within their component templates
92+
- **Global controllers** (in `app/javascript/controllers/`) can be used across any view
93+
- Pass data from Rails to Stimulus using `data-*-value` attributes, not inline JavaScript
94+
- Use Stimulus targets to reference DOM elements, not manual `getElementById` calls
95+
96+
- **Naming Conventions**
97+
- **Components**: Use `ComponentName` suffix (e.g., `ButtonComponent`, `DialogComponent`, `FilledIconComponent`)
98+
- **Partials**: Use underscore prefix (e.g., `_trend_change.html.erb`, `_form_errors.html.erb`, `_sync_indicator.html.erb`)
99+
- **Shared partials**: Place in `app/views/shared/` directory for reusable content
100+
- **Context-specific partials**: Place in relevant controller view directory (e.g., `accounts/_account_sidebar_tabs.html.erb`)

.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ APP_DOMAIN=
5151
# Disable enforcing SSL connections
5252
# DISABLE_SSL=true
5353

54+
# Active Record Encryption Keys (Optional)
55+
# These keys are used to encrypt sensitive data like API keys in the database.
56+
# If not provided, they will be automatically generated based on your SECRET_KEY_BASE.
57+
# You can generate your own keys by running: rails db:encryption:init
58+
# ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=
59+
# ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=
60+
# ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=
61+
5462
# ======================================================================================================
5563
# Active Storage Configuration - responsible for storing file uploads
5664
# ======================================================================================================

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ node_modules/
9898
tasks.json
9999
.taskmaster/tasks/
100100
.taskmaster/reports/
101+
.taskmaster/state.json
101102
*.mcp.json
102103
scripts/
103104
.cursor/mcp.json

0 commit comments

Comments
 (0)