# Tailwind And Phoenix Asset Tooling For bDS2 Desktop ## Purpose This document describes the target styling architecture for a Tailwind-integrated Phoenix LiveView desktop app in this repository. It is written as a handoff for a coding agent that will perform the implementation. This is not a migration guide from a public web app. It is a target-state guide for a Phoenix LiveView desktop shell with local assets, dense editor surfaces, overlays, resizable panes, and desktop-specific titlebar behavior. ## Verified Current State The current app does not use the default Phoenix asset pipeline. - CSS is served directly from `priv/ui/app.css`. - JS is served directly from `priv/ui/live.js`. - Static assets are served from `priv/ui` in `lib/bds/desktop/endpoint.ex`. - The root layout links `/assets/app.css` in `lib/bds/desktop/layouts.ex`, but that path is currently backed by `priv/ui/app.css`, not by generated Phoenix assets. - There is no `:tailwind` or `:esbuild` setup in `mix.exs`. Relevant files: - `lib/bds/desktop/endpoint.ex` - `lib/bds/desktop/layouts.ex` - `mix.exs` - `priv/ui/app.css` - `priv/ui/live.js` ## Official Phoenix Facts The implementation target should follow Phoenix defaults where they fit the desktop shell. Official Phoenix references: - Phoenix Asset Management: https://hexdocs.pm/phoenix/asset_management.html - Phoenix Components and HEEx: https://hexdocs.pm/phoenix/components.html - Phoenix.Component reference: https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html Facts from Phoenix docs and generator templates: - Phoenix v1.7+ defaults to Tailwind for CSS and esbuild for JS. - The default CSS source entrypoint is `assets/css/app.css`. - The default generated CSS output is `priv/static/assets/css/app.css`. - The default generated JS output is `priv/static/assets/js/app.js`. - Phoenix promotes function components and HEEx as the main rendering model. - Phoenix does not prescribe per-component CSS modules. Tailwind-first HEEx plus shared source CSS is a valid Phoenix-default shape. ## Target Outcome The target outcome is a solid Phoenix-default asset setup with Tailwind as the main styling system and a generated production stylesheet. The target should look like this: - Tailwind source lives in `assets/css/app.css` and imported CSS modules. - The final stylesheet is generated into `priv/static/assets/css/app.css`. - LiveView JS entrypoint lives in `assets/js/app.js` and builds to `priv/static/assets/js/app.js`. - The desktop endpoint serves static assets from `priv/static`. - Layouts reference the generated CSS and JS outputs. - HEEx markup carries most layout, spacing, typography, responsive, and state classes. - Authored CSS remains for global tokens, app-region behavior, pseudo-elements, scrollbars, Monaco integration, hard selectors, and overlay mechanics. ## Architecture Rules ### 1. Tailwind Owns The Common Case Use Tailwind utility classes directly in HEEx for: - flex and grid layout - spacing - typography - borders and radii - colors and opacity - active, selected, disabled, and hover states - responsive breakpoints - overflow and truncation Do not preserve large semantic wrapper classes when they only encode simple layout decisions. Good examples to move into HEEx classes: - tab rows - button rows - editor header layout - metadata field layout - sidebar list row spacing - status bar item alignment ### 2. Authored CSS Owns The Desktop-Specific Case Keep authored CSS source files for: - `app-region` and `-webkit-app-region` - pseudo-elements used for icons, handles, and active markers - custom scrollbars - Monaco/editor iframe or host integration - absolute overlay stacks and backdrops - drag and drop affordances - complex attribute selectors - hard-to-read repeated combinations that should become shared component classes Do not force these into giant utility strings if readability drops. ### 3. Keep A Thin Semantic CSS Layer It is acceptable to keep a small number of semantic component classes when they encode a repeated desktop UI primitive. Examples of valid semantic classes in the target state: - `.window-titlebar` - `.resizable-panel-divider` - `.overlay-root` - `.monaco-host` - `.panel-entry` - `.btn-base`, `.btn-theme-primary`, `.btn-theme-danger` These classes should be implemented in Tailwind source CSS using `@layer components` and should remain small, stable, and reusable. ### 4. Tokens Must Be Centralized The current stylesheet relies heavily on VS Code-like CSS variables. Preserve that idea. The new `assets/css/app.css` must define the design tokens once, using Tailwind v4 `@theme` plus any required raw CSS custom properties for runtime-driven values. Examples from the current app that must survive: - shell/background colors - tab active/inactive colors - status bar colors - focus border color - input background and border colors - sidebar width - assistant width - font family and font size ### 5. Desktop Layout Constraints Must Stay Intact The styling rewrite must preserve these runtime constraints: - the app occupies full window width and height - the shell uses `overflow: hidden` at the top level - the main workbench uses `min-height: 0` and `min-width: 0` correctly - resizable sidebars remain width-variable - titlebar remains draggable except for interactive controls - overlays render above the shell without breaking keyboard focus or pointer behavior ## Proposed Asset Layout Use the standard Phoenix asset layout. ```text assets/ css/ app.css shell.css sidebar.css tabs.css editor.css forms.css overlays.css panel.css assistant.css menu_editor.css media_editor.css utilities.css js/ app.js ... priv/ static/ assets/ css/ js/ ``` Recommended responsibilities: - `assets/css/app.css`: Tailwind import, `@source`, tokens, base layer, imports - `assets/css/shell.css`: app shell, titlebar, activity bar, pane shells, status bar - `assets/css/sidebar.css`: sidebar filters, search, chips, calendar tree, load more - `assets/css/tabs.css`: workbench tabs and editor tabs - `assets/css/editor.css`: common editor frame, toolbar, meta column, shared form shell - `assets/css/forms.css`: shared input, textarea, tag chip, picker, inline action primitives - `assets/css/overlays.css`: overlay root, modal backdrop, dialog shells, gallery/lightbox - `assets/css/panel.css`: panel tabs, panel entry cards, tasks, output, git log - `assets/css/assistant.css`: assistant sidebar and chat-specific shared surfaces - `assets/css/menu_editor.css`: menu tree, drag/drop indicators, picker lists - `assets/css/media_editor.css`: media preview, linked post picker, detail forms - `assets/css/utilities.css`: a very small set of custom utilities that are truly reused ## Proposed Phoenix Asset Tooling The implementation should introduce the standard Phoenix aliases and configs. ### mix.exs Add: - `{:tailwind, "~> 0.3", runtime: Mix.env() == :dev}` - `{:esbuild, "~> 0.10", runtime: Mix.env() == :dev}` Add aliases similar to: - `assets.setup` - `assets.build` - `assets.deploy` ### Versioning (mandatory) The Elixir `:tailwind` wrapper still defaults to Tailwind v3. The plan in this document assumes **Tailwind v4** syntax (`@import "tailwindcss"`, `@theme`, `@source`, `@layer components`). Pin both tools explicitly in `config/config.exs`: - `config :tailwind, version: "4.1.14"` (or current 4.1.x) - `config :esbuild, version: "0.25.4"` (or current 0.25.x) Without an explicit v4 pin the build will silently install Tailwind v3 and v4 directives will not resolve. ### No Node.js policy The Elixir `:tailwind` and `:esbuild` wrappers download self-contained binaries and do **not** require Node.js. The implementation MUST stay Node-free unless a third-party Tailwind plugin is later required (in which case the custom `assets/build.js` route from the Phoenix asset_management guide is used). No `package.json` is added under `assets/`. ### config/config.exs Configure Tailwind input and output paths. Target output: - `assets/css/app.css` -> `priv/static/assets/css/app.css` Configure esbuild for: - `assets/js/app.js` -> `priv/static/assets/js/app.js` esbuild profile must include `--bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/*` and `nodePaths: [Mix.Project.build_path() <> "/../../deps"]` so `phoenix`, `phoenix_html`, and `phoenix_live_view` resolve from `deps/` without an `npm install`. ### config/dev.exs Add Phoenix watchers for: - Tailwind `--watch` - esbuild `--watch` ### No phx.digest in desktop builds This is a desktop app served through an embedded WebView, not a public web app behind a CDN. - Do NOT run `mix phx.digest` as part of `assets.deploy`. - Output filenames stay stable (`app.css`, `app.js`) so the layout can link them by fixed path. - `assets.deploy` for this repo is: `tailwind default --minify`, `esbuild default --minify`. Nothing else. ### endpoint/layout changes Update the desktop endpoint and root layout to serve and link generated assets from `priv/static/assets` instead of `priv/ui`. Specifically: - Replace the existing `Plug.Static` for `/assets` with `from: {:bds, "priv/static/assets"}` and `only` listing the generated `css` and `js` directories. - Drop the `/vendor/phoenix` and `/vendor/live_view` `Plug.Static` blocks; those scripts are now bundled by esbuild from `deps/`. - Add a dedicated `Plug.Static` for `/monaco` pointing at `priv/ui/monaco` (or move it to `priv/static/monaco`). Monaco is a prebuilt vendor drop and MUST NOT be passed through esbuild. - Remove the `