aprs.me/CLAUDE.md

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.md for 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, or List for index based list access, ie:

    i = 0
    mylist = ["blue", "green"]
    Enum.at(mylist, i)
    
  • Elixir supports if/else but **does NOT support if/else if or if/elsif. Never use else if or elseif in Elixir, always use cond or case for 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 with for 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 as my_struct.field or use higher level APIs that are available on the struct if they exist, Ecto.Changeset.get_field/2 for changesets

  • Elixir's standard library has everything necessary for date and time manipulation. Familiarize yourself with the common Time, Date, DateTime, and Calendar interfaces by accessing their documentation as necessary. Never install additional dependencies unless asked or for date/time parsing (which you can use the date_time_parser package)

  • Don't use String.to_atom/1 on user input (memory leak risk)

  • Predicate function names should not start with is_ and should end in a question mark. Names like is_thing should be reserved for guards

  • Elixir's builtin OTP primitives like DynamicSupervisor and Registry, require names in the child spec, such as {DynamicSupervisor, name: MyApp.MyDynamicSup}, then you can use DynamicSupervisor.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 pass timeout: :infinity as option

  • Always run mix format after changing any Elixir files

Browser / UI Verification

  • Always use Tidewave's browser_eval tool 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() and browser.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