add backend for join form #308 #438
1 changed files with 18 additions and 3 deletions
|
|
@ -385,6 +385,8 @@ def process_user(user), do: {:ok, perform_action(user)}
|
|||
|
||||
### 2.3 Error Handling
|
||||
|
||||
**No silent failures:** When an error path assigns a fallback (e.g. empty list, unchanged assigns), always log the error with enough context (e.g. `inspect(error)`, slug, action) and/or set a user-visible flash. Do not only assign the fallback without logging.
|
||||
|
||||
**Use Tagged Tuples:**
|
||||
|
||||
```elixir
|
||||
|
|
@ -623,6 +625,10 @@ defmodule MvWeb.MemberLive.Index do
|
|||
end
|
||||
```
|
||||
|
||||
**LiveView load budget:** Keep UI-triggered events cheap. `phx-change`, `phx-focus`, and `phx-keydown` must **not** perform database reads by default; work from assigns (e.g. filter in memory) or defer reads to an explicit commit step (e.g. "Add", "Save", "Submit"). Perform DB reads or reloads only on commit events, not on every keystroke or focus. If a read in an event is unavoidable, do at most one deliberate read, document why, and prefer debounce/throttle.
|
||||
|
||||
**LiveView size:** When a LiveView accumulates many features and event handlers (CRUD + add/remove + search + keyboard + modals), extract sub-flows into LiveComponents. The parent keeps auth, initial load, and a single reload after child actions; the component owns the sub-flow and notifies the parent when data changes.
|
||||
|
||||
**Component Design:**
|
||||
|
||||
```elixir
|
||||
|
|
@ -1267,6 +1273,9 @@ gettext("Welcome to Mila")
|
|||
# With interpolation
|
||||
gettext("Hello, %{name}!", name: user.name)
|
||||
|
||||
# Plural: always pass count binding when message uses %{count}
|
||||
ngettext("Found %{count} member", "Found %{count} members", @count, count: @count)
|
||||
|
||||
# Domain-specific translations
|
||||
dgettext("auth", "Sign in with email")
|
||||
```
|
||||
|
|
@ -1507,6 +1516,8 @@ defmodule MvWeb.MemberLive.IndexTest do
|
|||
end
|
||||
```
|
||||
|
||||
**LiveView test standards:** Prefer selector-based assertions (`has_element?` on `data-testid`, stable IDs, or semantic markers) over free-text matching (`html =~ "..."`) or broad regex. For repeated flows (e.g. "open add member", "search", "select"), use helpers in `test/support/`. If delete is a LiveView event, test that event and assert both UI and data; if delete uses JS `data-confirm` + non-LV submit, cover the real delete path in a context/service test and add at most one smoke test in the UI. Do not assert or document "single request" or "DB-level sort" without measuring (e.g. query count or timing).
|
||||
|
||||
#### 4.3.5 Component Tests
|
||||
|
||||
Test function components:
|
||||
|
|
@ -1876,6 +1887,8 @@ policies do
|
|||
end
|
||||
```
|
||||
|
||||
**Record-based authorization:** When a concrete record is available, use `can?(actor, :action, record)` (e.g. `can?(@current_user, :update, @group)`). Use `can?(actor, :action, Resource)` only when no specific record exists (e.g. "can create any group"). In events: resolve the record from assigns, run `can?`, then mutate; avoid an extra DB read just for a "freshness" check if the assign is already the source of truth.
|
||||
|
||||
**Actor Handling in LiveViews:**
|
||||
|
||||
Always use the `current_actor/1` helper for consistent actor access:
|
||||
|
|
@ -2707,7 +2720,9 @@ Building accessible applications ensures that all users, including those with di
|
|||
|
||||
### 8.2 ARIA Labels and Roles
|
||||
|
||||
**Use ARIA Attributes When Necessary:**
|
||||
**Terminology and semantics:** Use the same terms in UI, tests, and docs (e.g. "modal" vs "inline confirmation"). If the implementation is inline, do not call it a modal in tests or docs.
|
||||
|
||||
**Use ARIA Attributes When Necessary:** Use `role="status"` only for live regions that present advisory status (e.g. search result count, progress). Do not use it on links, buttons, or purely static labels; use semantic HTML or the correct role for the widget. Attach `phx-debounce` / `phx-throttle` to the element that triggers the event (e.g. input), not to the whole form, unless the intent is form-wide.
|
||||
|
||||
```heex
|
||||
<!-- Icon-only buttons need labels -->
|
||||
|
|
@ -2931,11 +2946,11 @@ end
|
|||
**Announce Dynamic Content:**
|
||||
|
||||
```heex
|
||||
<!-- Search results announcement -->
|
||||
<!-- Search results announcement (count: required so %{count} is replaced and pluralisation works) -->
|
||||
<div role="status" aria-live="polite" aria-atomic="true">
|
||||
<%= if @searched do %>
|
||||
<span class="sr-only">
|
||||
<%= ngettext("Found %{count} member", "Found %{count} members", @count) %>
|
||||
<%= ngettext("Found %{count} member", "Found %{count} members", @count, count: @count) %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue