Skip to content

LiveView-style ergonomics: :if/:for, @assigns sugar, Socket helpers#52

Merged
GenericJam merged 2 commits into
masterfrom
feat/liveview-ergonomics
Jun 27, 2026
Merged

LiveView-style ergonomics: :if/:for, @assigns sugar, Socket helpers#52
GenericJam merged 2 commits into
masterfrom
feat/liveview-ergonomics

Conversation

@GenericJam

Copy link
Copy Markdown
Owner

Imports a few authoring ergonomics from LiveView so Mob screens read the way Phoenix developers expect. This is the "feel" layer only — no DOM-specific bits (phx-*, JS commands, morphdom, streams are out of scope).

What's in

~MOB sigil (lib/mob/sigil.ex)

  • @foo shorthand — inside any {...} expression, @foo rewrites to assigns.foo (attribute values, {expr} children, and control expressions alike), matching Phoenix HEEx. Nested forms like @user.name work too.
  • :if={expr} — renders the element only when the expression is truthy. A falsy :if yields nil, which is dropped from the parent's children.
  • :for={x <- list} — repeats the element per item and splices into the parent.
  • :for + :if combined:if becomes a comprehension filter (LiveView semantics): an element is produced only for items where the condition holds.

Implementation notes:

  • A leading : is now accepted in attribute names at the parser level; split_control_attrs/1 pulls :if/:for out before props are built and raises on any other :-prefixed attr.
  • :if/:for require a {expr} value (a string value raises a clear CompileError).
  • wrap_child/1 now normalizes nil (dropped) alongside list/map, so control-wrapped children flatten cleanly through the existing List.flatten.
  • Branching in wrap_control/3 is on attr presence, not the parsed expr — otherwise :if={false} would short-circuit and skip wrapping.

Mob.Socket (lib/mob/socket.ex)

  • update/3 — apply a function to an existing assign (KeyError if absent)
  • assign_new/3 — lazily set an assign only when absent

Both mirror Phoenix.LiveView.

Tests

Per the repo rule that sigil compile-time transforms are tested at the AST level, not just runtime: new coverage exercises self-closing and container elements, the :if/:for filter combo, @ expansion (including nested @user.name), empty-list :for, and compile-time errors for unknown/ string-valued control attrs.

  • Full suite: 960 passed
  • mix format, mix credo --strict, mix erlfmt --check src/: clean

Not included

No version bump — release timing is the maintainer's call.

🤖 Generated with Claude Code

GenericJam and others added 2 commits June 26, 2026 22:29
Imports authoring ergonomics from LiveView into the ~MOB sigil and
Mob.Socket, so screens read the way Phoenix developers expect.

Sigil (lib/mob/sigil.ex):
- `@foo` inside any `{...}` expression rewrites to `assigns.foo`
  (attribute values, {expr} children, and control expressions alike).
- `:if={expr}` wraps an element so it renders only when truthy; a
  falsy `:if` yields nil, which is dropped from the parent's children.
- `:for={x <- list}` repeats an element per item and splices into the
  parent. Combined with `:if`, the `:if` becomes a comprehension filter
  (LiveView semantics).
- wrap_child/1 now normalizes nil (dropped) alongside list/map, so
  control-wrapped children flatten cleanly.

Socket (lib/mob/socket.ex):
- update/3 (apply a fun to an existing assign, KeyError if absent)
- assign_new/3 (lazily set an assign only when absent)
  Both mirror Phoenix.LiveView.

Tests cover the generated AST and runtime behavior for self-closing and
container elements, the :if/:for filter combo, @ expansion (including
nested @user.name), and compile-time errors for bad control attrs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The code carried good moduledocs but the agent-facing guides (read_guide /
get_oriented point here, not hexdocs) didn't mention the new authoring idioms,
making them undiscoverable through the normal learning path.

- guides/components.md: new "## Control flow" section documenting @assigns
  shorthand, :if, :for, and the :for+:if comprehension-filter combo, plus the
  compile-time error cases.
- guides/liveview.md: disambiguation note — that guide is WebView LiveView
  mode; point readers after native sigil "feel" to Components → Control flow.
- AGENTS.md: one-line authoring convention pointing at the new section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@GenericJam GenericJam merged commit 5a133f5 into master Jun 27, 2026
4 checks passed
GenericJam added a commit that referenced this pull request Jun 27, 2026
~MOB sigil gains @assigns shorthand and :if / :for control attributes;
Mob.Socket gains update/3 and assign_new/3. See guides/components.md
"Control flow". (#52)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@GenericJam GenericJam deleted the feat/liveview-ergonomics branch July 5, 2026 02:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant