From d97918d66a8efc24f2825ec33f6e1df1ac2acced Mon Sep 17 00:00:00 2001 From: jamey Date: Sat, 31 Jan 2026 14:06:07 +0000 Subject: [PATCH] docs: consolidate project tracking into PROGRESS.md - Create PROGRESS.md as single source of truth for status - Slim ROADMAP.md to vision only (~100 lines, down from ~500) - Expand CLAUDE.md with streams, auth routing, forms, workflow - Convert AGENTS.md to stub pointing to CLAUDE.md - Update plan files with status headers, remove progress trackers Co-Authored-By: Claude Opus 4.5 --- AGENTS.md | 396 +-------------------------- CLAUDE.md | 126 ++++++++- PROGRESS.md | 106 +++++++ ROADMAP.md | 455 ++----------------------------- docs/plans/image-optimization.md | 25 +- docs/plans/products-context.md | 2 + 6 files changed, 252 insertions(+), 858 deletions(-) create mode 100644 PROGRESS.md diff --git a/AGENTS.md b/AGENTS.md index 879cc1d..deb2760 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,395 +1,5 @@ -This is a web application written using the Phoenix web framework. +# Project Guidelines -## Project guidelines +See [CLAUDE.md](CLAUDE.md) for comprehensive project guidelines. -- Use `mix precommit` alias when you are done with all changes and fix any pending issues -- Use the already included and available `:req` (`Req`) library for HTTP requests, **avoid** `:httpoison`, `:tesla`, and `:httpc`. Req is included by default and is the preferred HTTP client for Phoenix apps - -### Phoenix v1.8 guidelines - -- **Always** begin your LiveView templates with `` which wraps all inner content -- The `MyAppWeb.Layouts` module is aliased in the `my_app_web.ex` file, so you can use it without needing to alias it again -- Anytime you run into errors with no `current_scope` assign: - - You failed to follow the Authenticated Routes guidelines, or you failed to pass `current_scope` to `` - - **Always** fix the `current_scope` error by moving your routes to the proper `live_session` and ensure you pass `current_scope` as needed -- Phoenix v1.8 moved the `<.flash_group>` component to the `Layouts` module. You are **forbidden** from calling `<.flash_group>` outside of the `layouts.ex` module -- Out of the box, `core_components.ex` imports an `<.icon name="hero-x-mark" class="w-5 h-5"/>` component for for hero icons. **Always** use the `<.icon>` component for icons, **never** use `Heroicons` modules or similar -- **Always** use the imported `<.input>` component for form inputs from `core_components.ex` when available. `<.input>` is imported and using it will will save steps and prevent errors -- If you override the default input classes (`<.input class="myclass px-2 py-1 rounded-lg">)`) class with your own values, no default classes are inherited, so your -custom classes must fully style the input - -### JS and CSS guidelines - -- **Use Tailwind CSS classes and custom CSS rules** to create polished, responsive, and visually stunning interfaces. -- Tailwindcss v4 **no longer needs a tailwind.config.js** and uses a new import syntax in `app.css`: - - @import "tailwindcss" source(none); - @source "../css"; - @source "../js"; - @source "../../lib/my_app_web"; - -- **Always use and maintain this import syntax** in the app.css file for projects generated with `phx.new` -- **Never** use `@apply` when writing raw css -- **Always** manually write your own tailwind-based components instead of using daisyUI for a unique, world-class design -- Out of the box **only the app.js and app.css bundles are supported** - - You cannot reference an external vendor'd script `src` or link `href` in the layouts - - You must import the vendor deps into app.js and app.css to use them - - **Never write inline tags within templates** - -### UI/UX & design guidelines - -- **Produce world-class UI designs** with a focus on usability, aesthetics, and modern design principles -- Implement **subtle micro-interactions** (e.g., button hover effects, and smooth transitions) -- Ensure **clean typography, spacing, and layout balance** for a refined, premium look -- Focus on **delightful details** like hover effects, loading states, and smooth page transitions - - - -## Authentication - -- **Always** handle authentication flow at the router level with proper redirects -- **Always** be mindful of where to place routes. `phx.gen.auth` creates multiple router plugs and `live_session` scopes: - - A plug `:fetch_current_scope_for_user` that is included in the default browser pipeline - - A plug `:require_authenticated_user` that redirects to the log in page when the user is not authenticated - - A `live_session :current_user` scope - for routes that need the current user but don't require authentication, similar to `:fetch_current_scope_for_user` - - A `live_session :require_authenticated_user` scope - for routes that require authentication, similar to the plug with the same name - - In both cases, a `@current_scope` is assigned to the Plug connection and LiveView socket - - A plug `redirect_if_user_is_authenticated` that redirects to a default path in case the user is authenticated - useful for a registration page that should only be shown to unauthenticated users -- **Always let the user know in which router scopes, `live_session`, and pipeline you are placing the route, AND SAY WHY** -- `phx.gen.auth` assigns the `current_scope` assign - it **does not assign a `current_user` assign** -- Always pass the assign `current_scope` to context modules as first argument. When performing queries, use `current_scope.user` to filter the query results -- To derive/access `current_user` in templates, **always use the `@current_scope.user`**, never use **`@current_user`** in templates or LiveViews -- **Never** duplicate `live_session` names. A `live_session :current_user` can only be defined __once__ in the router, so all routes for the `live_session :current_user` must be grouped in a single block -- Anytime you hit `current_scope` errors or the logged in session isn't displaying the right content, **always double check the router and ensure you are using the correct plug and `live_session` as described below** - -### Routes that require authentication - -LiveViews that require login should **always be placed inside the __existing__ `live_session :require_authenticated_user` block**: - - scope "/", AppWeb do - pipe_through [:browser, :require_authenticated_user] - - live_session :require_authenticated_user, - on_mount: [{SimpleshopThemeWeb.UserAuth, :require_authenticated}] do - # phx.gen.auth generated routes - live "/users/settings", UserLive.Settings, :edit - live "/users/settings/confirm-email/:token", UserLive.Settings, :confirm_email - # our own routes that require logged in user - live "/", MyLiveThatRequiresAuth, :index - end - end - -Controller routes must be placed in a scope that sets the `:require_authenticated_user` plug: - - scope "/", AppWeb do - pipe_through [:browser, :require_authenticated_user] - - get "/", MyControllerThatRequiresAuth, :index - end - -### Routes that work with or without authentication - -LiveViews that can work with or without authentication, **always use the __existing__ `:current_user` scope**, ie: - - scope "/", MyAppWeb do - pipe_through [:browser] - - live_session :current_user, - on_mount: [{SimpleshopThemeWeb.UserAuth, :mount_current_scope}] do - # our own routes that work with or without authentication - live "/", PublicLive - end - end - -Controllers automatically have the `current_scope` available if they use the `:browser` pipeline. - - - - - - -## 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 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 - -- **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 - -## Mix guidelines - -- Read the docs and options before using tasks (by using `mix help task_name`) -- To debug test failures, run tests in a specific file with `mix test test/my_test.exs` or run all previously failed tests with `mix test --failed` -- `mix deps.clean --all` is **almost never needed**. **Avoid** using it unless you have good reason - - - -## Phoenix guidelines - -- Remember Phoenix router `scope` blocks include an optional alias which is prefixed for all routes within the scope. **Always** be mindful of this when creating routes within a scope to avoid duplicate module prefixes. - -- You **never** need to create your own `alias` for route definitions! The `scope` provides the alias, ie: - - scope "/admin", AppWeb.Admin do - pipe_through :browser - - live "/users", UserLive, :index - end - - the UserLive route would point to the `AppWeb.Admin.UserLive` module - -- `Phoenix.View` no longer is needed or included with Phoenix, don't use it - - - -## Ecto Guidelines - -- **Always** preload Ecto associations in queries when they'll be accessed in templates, ie a message that needs to reference the `message.user.email` -- Remember `import Ecto.Query` and other supporting modules when you write `seeds.exs` -- `Ecto.Schema` fields always use the `:string` type, even for `:text`, columns, ie: `field :name, :string` -- `Ecto.Changeset.validate_number/2` **DOES NOT SUPPORT the `:allow_nil` option**. By default, Ecto validations only run if a change for the given field exists and the change value is not nil, so such as option is never needed -- You **must** use `Ecto.Changeset.get_field(changeset, :field)` to access changeset fields -- Fields which are set programatically, such as `user_id`, must not be listed in `cast` calls or similar for security purposes. Instead they must be explicitly set when creating the struct - - - -## Phoenix HTML guidelines - -- Phoenix templates **always** use `~H` or .html.heex files (known as HEEx), **never** use `~E` -- **Always** use the imported `Phoenix.Component.form/1` and `Phoenix.Component.inputs_for/1` function to build forms. **Never** use `Phoenix.HTML.form_for` or `Phoenix.HTML.inputs_for` as they are outdated -- When building forms **always** use the already imported `Phoenix.Component.to_form/2` (`assign(socket, form: to_form(...))` and `<.form for={@form} id="msg-form">`), then access those forms in the template via `@form[:field]` -- **Always** add unique DOM IDs to key elements (like forms, buttons, etc) when writing templates, these IDs can later be used in tests (`<.form for={@form} id="product-form">`) -- For "app wide" template imports, you can import/alias into the `my_app_web.ex`'s `html_helpers` block, so they will be available to all LiveViews, LiveComponent's, and all modules that do `use MyAppWeb, :html` (replace "my_app" by the actual app name) - -- 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 %> - -- HEEx require special tag annotation if you want to insert literal curly's like `{` or `}`. If you want to show a textual code snippet on the page in a `
` or `` block you *must* annotate the parent tag with `phx-no-curly-interpolation`:
-
-      
-        let obj = {key: "val"}
-      
-
-  Within `phx-no-curly-interpolation` annotated tags, you can use `{` and `}` without escaping them, and dynamic Elixir expressions can still be used with `<%= ... %>` syntax
-
-- HEEx class attrs support lists, but you must **always** use list `[...]` syntax. You can use the class list syntax to conditionally add classes, **always do this for multiple class values**:
-
-      Text
-
-  and **always** wrap `if`'s inside `{...}` expressions with parens, like done above (`if(@other_condition, do: "...", else: "...")`)
-
-  and **never** do this, since it's invalid (note the missing `[` and `]`):
-
-       ...
-      => Raises compile syntax error on invalid HEEx attr syntax
-
-- **Never** use `<% Enum.each %>` or non-for comprehensions for generating template content, instead **always** use `<%= for item <- @collection do %>`
-- HEEx HTML comments use `<%!-- comment --%>`. **Always** use the HEEx HTML comment syntax for template comments (`<%!-- comment --%>`)
-- HEEx allows interpolation via `{...}` and `<%= ... %>`, but the `<%= %>` **only** works within tag bodies. **Always** use the `{...}` syntax for interpolation within tag attributes, and for interpolation of values within tag bodies. **Always** interpolate block constructs (if, cond, case, for) within tag bodies using `<%= ... %>`.
-
-  **Always** do this:
-
-      
- {@my_assign} - <%= if @some_block_condition do %> - {@another_assign} - <% end %> -
- - and **Never** do this – the program will terminate with a syntax error: - - <%!-- THIS IS INVALID NEVER EVER DO THIS --%> -
- {if @invalid_block_construct do} - {end} -
- - - -## Phoenix LiveView guidelines - -- **Never** use the deprecated `live_redirect` and `live_patch` functions, instead **always** use the `<.link navigate={href}>` and `<.link patch={href}>` in templates, and `push_navigate` and `push_patch` functions LiveViews -- **Avoid LiveComponent's** unless you have a strong, specific need for them -- LiveViews should be named like `AppWeb.WeatherLive`, with a `Live` suffix. When you go to add LiveView routes to the router, the default `:browser` scope is **already aliased** with the `AppWeb` module, so you can just do `live "/weather", WeatherLive` -- Remember anytime you use `phx-hook="MyHook"` and that js hook manages its own DOM, you **must** also set the `phx-update="ignore"` attribute -- **Never** write embedded `