4.1 KiB
This is a web application written using the Phoenix web framework.
Mobile API
The application provides a real-time WebSocket API for mobile clients (iOS/Android):
- Endpoint:
wss://aprs.me/mobile/websocket - Channel:
mobile:packets - Documentation: See
docs/mobile-api.mdfor complete API reference - Implementation:
lib/aprsme_web/channels/mobile_channel.ex
Key Features:
- Real-time streaming of APRS packets
- Geographic bounds filtering (only receive packets in viewport)
- Dynamic bounds updates (follow user as they pan/zoom)
- Efficient - leverages existing StreamingPacketsPubSub infrastructure
Elixir guidelines
-
Elixir lists do not support index based access via the access syntax
Never do this (invalid):
i = 0 mylist = ["blue", "green"] mylist[i]Instead, always use
Enum.at, pattern matching, orListfor index based list access, ie:i = 0 mylist = ["blue", "green"] Enum.at(mylist, i) -
Elixir supports
if/elsebut **does NOT supportif/else iforif/elsif. Never useelse iforelseifin Elixir, always usecondorcasefor multiple conditionals.Never do this (invalid):
<%= if condition do %> ... <% else if other_condition %> ... <% end %>Instead always do this:
<%= cond do %> <% condition -> %> ... <% condition2 -> %> ... <% true -> %> ... <% end %> -
Elixir variables are immutable, but can be rebound, so for block expressions like
if,case,cond, etc you must bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie:# INVALID: we are rebinding inside the `if` and the result never gets assigned if connected?(socket) do socket = assign(socket, :val, val) end # VALID: we rebind the result of the `if` to a new variable socket = if connected?(socket) do assign(socket, :val, val) end -
Use
withfor chaining operations that return{:ok, _}or{:error, _} -
Never nest multiple modules in the same file as it can cause cyclic dependencies and compilation errors
-
Never use map access syntax (
changeset[:field]) on structs as they do not implement the Access behaviour by default. For regular structs, you must access the fields directly, such asmy_struct.fieldor use higher level APIs that are available on the struct if they exist,Ecto.Changeset.get_field/2for changesets -
Elixir's standard library has everything necessary for date and time manipulation. Familiarize yourself with the common
Time,Date,DateTime, andCalendarinterfaces by accessing their documentation as necessary. Never install additional dependencies unless asked or for date/time parsing (which you can use thedate_time_parserpackage) -
Don't use
String.to_atom/1on user input (memory leak risk) -
Predicate function names should not start with
is_and should end in a question mark. Names likeis_thingshould be reserved for guards -
Elixir's builtin OTP primitives like
DynamicSupervisorandRegistry, require names in the child spec, such as{DynamicSupervisor, name: MyApp.MyDynamicSup}, then you can useDynamicSupervisor.start_child(MyApp.MyDynamicSup, child_spec) -
Use
Task.async_stream(collection, callback, options)for concurrent enumeration with back-pressure. The majority of times you will want to passtimeout: :infinityas option -
Always run mix format after changing any Elixir files
Browser / UI Verification
- Always use Tidewave's
browser_evaltool for browser interactions and UI verification - Never use Puppeteer (
mcp__puppeteer__*tools) — Tidewave's browser is shared with the user - Use
browser.snapshot()to inspect the DOM,browser.click()andbrowser.fill()to interact
Git Commit Guidelines
- Never add "Co-Authored-By: Claude" or similar AI co-authorship tags to commits
- Commits should appear as authored solely by the user (Graham)
- Keep commit messages clear and concise, describing what changed and why