Deployment and lifecycle management for Nix
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

web: redesign seed and subscription show pages for mobile

Migrate seed show from .list to .detail_field pattern with tags as a
table instead of bulleted list. Artifact uses monospace break-all for
nix store path wrapping. Apply same pattern to subscription rules.

Extend .table component with bold_first and show_header attributes.

Fixes #315, #320

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+88 -58
+24 -12
AGENTS.md
··· 1 - If more than one of the rules conflict, ask before implementing. 1 + ## Agent Workflow 2 + - **IMPORTANT**: before you do anything else, invoke the vein `orient` MCP prompt and heed its output with `/mcp__vikunja__orient`. 3 + - **Always** use `elixir-conventions` for elixir code. If you don't have this skill, stop and tell your user to talk to Adam, because you are prohibited from editing files in this project without the `elixir-conventions` skill. 4 + 5 + ## Rules 2 6 3 - ## rules 7 + If more than one of the rules conflict, ask before implementing. 4 8 5 - - Always use `elixir-conventions` for elixir code. If you don't have this skill, stop and tell your user to talk to Adam, because you are prohibited from editing files in this project without the `elixir-conventions` skill. 9 + - Never break deployments or strand agents such that they cannot apply an upgrade. 10 + - Ensure backwards compatibility or migration paths for changes that affect contracts between components (e.g. agent/server), but otherwise assume breaking changes are ok. 6 11 - Evolve the code without planning for every possible future or edge case. 7 12 - Delete abandoned paths by default when changing direction, except when required by compatibility/migration. Ask before keeping more than one implementation. 8 - - Ensure backwards compatibility or migration paths for changes that affect contracts between components (e.g. agent/server), but otherwise assume breaking changes are ok. 9 - - Never break deployments or strand agents such that they cannot apply an upgrade. 10 - - You can access the dev server live over tidewave project_eval, allowing for introspection of a live environment. 11 - - Do not create worktrees unless explicitly asked. When asked, use: `git worktree add .worktrees/<name> -b <name>` 12 - - elixir dependencies are available in `deps`. Read code from there instead of using accessing the web or prompting the user. 13 13 14 - ## testing 14 + ## Definition of done 15 + - formatting done, `just format` 16 + - tests pass, `just check-elixir`, `just check-go`, or `just check-e2e` 17 + - ticket marked complete 18 + - code committed with all ticket changes included 19 + - Ticket ID in the body 20 + - Co-Authored-By line always included 15 21 16 - - test nix end to end with `just check-e2e` 22 + ## Code conventions 17 23 24 + - Always read code for project elixir dependencies from `deps`. Never query hexdocs or hex. 18 25 19 - ## Worktree setup 26 + ## Testing 27 + 28 + - Test nix end to end with `just check-e2e` 29 + - You can access the dev server live over tidewave project_eval, allowing for introspection of a live environment. 30 + 31 + ## Workspace setup 20 32 21 - After creating a worktree, run: `mix deps.get && mix compile` 33 + After creating a workspace, run: `mix deps.get && mix compile`
+5 -3
apps/sower/lib/sower_web/components/sower_components.ex
··· 22 22 23 23 attr :action_hide_on, :atom, default: nil 24 24 attr :header_border, :boolean, default: true 25 + attr :bold_first, :boolean, default: true 26 + attr :show_header, :boolean, default: true 25 27 slot :action 26 28 27 29 def table(assigns) do ··· 32 34 33 35 ~H""" 34 36 <div class="px-4 sm:overflow-visible sm:px-0"> 35 - <table class="w-full mt-11"> 36 - <thead class="text-sm text-left leading-6 text-zinc-500 dark:text-zinc-400"> 37 + <table class={["w-full", @show_header && "mt-11"]}> 38 + <thead :if={@show_header} class="text-sm text-left leading-6 text-zinc-500 dark:text-zinc-400"> 37 39 <tr> 38 40 <th 39 41 :for={col <- @col} ··· 76 78 > 77 79 <div class="block py-4 pr-6"> 78 80 <span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 dark:group-hover:bg-zinc-800" /> 79 - <span class={["relative", i == 0 && "font-semibold"]}> 81 + <span class={["relative", @bold_first && i == 0 && "font-semibold"]}> 80 82 {render_slot(col, @row_item.(row))} 81 83 </span> 82 84 </div>
+33 -22
apps/sower/lib/sower_web/live/seed_live/show.html.heex
··· 1 1 <Layouts.app flash={@flash} current_user={@current_user}> 2 2 <.header> 3 3 {@seed.name} 4 - <:subtitle>{@seed.seed_type}</:subtitle> 5 4 </.header> 6 5 7 - <.list> 8 - <:item title="seed type">{@seed.seed_type}</:item> 9 - <:item title="artifact">{@seed.artifact}</:item> 10 - <:item title="tags"> 11 - <%= if Enum.empty?(@seed.tags) do %> 12 - <span class="text-zinc-500 dark:text-zinc-400">No tags</span> 13 - <% else %> 14 - <ul class="list-disc list-inside space-y-0.5"> 15 - <%= for tag <- Enum.sort(@seed.tags) do %> 16 - <li> 17 - {tag.key}={tag.value} 18 - </li> 19 - <% end %> 20 - </ul> 21 - <% end %> 22 - </:item> 23 - <:item title="created"> 6 + <div class="mt-8 space-y-10"> 7 + <.detail_field label="Seed Type">{@seed.seed_type}</.detail_field> 8 + 9 + <.detail_field label="Artifact"> 10 + <span class="font-mono text-sm break-all">{@seed.artifact}</span> 11 + </.detail_field> 12 + 13 + <section class="-mx-4 sm:mx-0"> 14 + <h2 class="text-sm font-semibold text-zinc-900 dark:text-zinc-200 mb-4 px-4 sm:px-0"> 15 + Tags 16 + </h2> 17 + <.table id="seed-tags" rows={Enum.sort(@seed.tags)} bold_first={false} show_header={false}> 18 + <:col :let={tag} label="Key">{tag.key}</:col> 19 + <:col :let={tag} label="Value"> 20 + <span class="truncate block max-w-[200px] sm:max-w-md" title={tag.value}> 21 + {tag.value} 22 + </span> 23 + </:col> 24 + </.table> 25 + <p 26 + :if={Enum.empty?(@seed.tags)} 27 + class="text-sm text-zinc-500 dark:text-zinc-400 italic" 28 + > 29 + No tags. 30 + </p> 31 + </section> 32 + 33 + <.detail_field label="Created"> 24 34 <.local_datetime datetime={@seed.inserted_at} user_timezone={@user_timezone} /> 25 - </:item> 26 - <:item title="updated"> 35 + </.detail_field> 36 + 37 + <.detail_field label="Updated"> 27 38 <.local_datetime datetime={@seed.updated_at} user_timezone={@user_timezone} /> 28 - </:item> 29 - </.list> 39 + </.detail_field> 40 + </div> 30 41 </Layouts.app>
+26 -21
apps/sower/lib/sower_web/live/subscription_live/show.html.heex
··· 1 1 <Layouts.app flash={@flash} current_user={@current_user}> 2 2 <.header> 3 - Subscription {@subscription.id} 4 - <:subtitle>This is a subscription record from your database.</:subtitle> 3 + {@subscription.sid} 5 4 <:actions> 6 5 <.deploy_button 7 6 subscription_sid={@subscription.sid} ··· 9 8 deploying={@deploying} 10 9 deploy_error={@deploy_error} 11 10 /> 12 - <.link 13 - patch={~p"/agents/#{@agent}/subscriptions/#{@subscription}/show/edit"} 14 - phx-click={JS.push_focus()} 15 - > 16 - <.button>Edit subscription</.button> 17 - </.link> 18 11 </:actions> 19 12 </.header> 20 13 ··· 32 25 33 26 <.detail_field label="Seed Type">{@subscription.seed_type}</.detail_field> 34 27 35 - <.detail_field label="Rules"> 36 - <%= if Enum.empty?(@subscription.rules) do %> 37 - <span class="text-zinc-500 dark:text-zinc-400">No rules</span> 38 - <% else %> 39 - <ul class="list-disc list-inside space-y-0.5"> 40 - <%= for rule <- @subscription.rules do %> 41 - <li> 42 - {SowerClient.SubscriptionRuleFormat.print(rule)} 43 - </li> 44 - <% end %> 45 - </ul> 46 - <% end %> 47 - </.detail_field> 28 + <section class="-mx-4 sm:mx-0"> 29 + <h2 class="text-sm font-semibold text-zinc-900 dark:text-zinc-200 mb-4 px-4 sm:px-0"> 30 + Rules 31 + </h2> 32 + <.table 33 + id="subscription-rules" 34 + rows={@subscription.rules} 35 + bold_first={false} 36 + show_header={false} 37 + > 38 + <:col :let={rule}>{rule.key}</:col> 39 + <:col :let={rule}>{rule.op}</:col> 40 + <:col :let={rule}> 41 + <span class="truncate block max-w-[200px] sm:max-w-md" title={rule.value}> 42 + {rule.value} 43 + </span> 44 + </:col> 45 + </.table> 46 + <p 47 + :if={Enum.empty?(@subscription.rules)} 48 + class="text-sm text-zinc-500 dark:text-zinc-400 italic" 49 + > 50 + No rules. 51 + </p> 52 + </section> 48 53 49 54 <section> 50 55 <h2 class="text-sm font-semibold text-zinc-900 dark:text-zinc-200 mb-4">Matching Seeds</h2>