Skip to content

feat(projects): group Servers as an organizational lens#31

Merged
pachev merged 10 commits into
mainfrom
claude/quizzical-faraday-c39926
May 23, 2026
Merged

feat(projects): group Servers as an organizational lens#31
pachev merged 10 commits into
mainfrom
claude/quizzical-faraday-c39926

Conversation

@pachev
Copy link
Copy Markdown
Owner

@pachev pachev commented May 23, 2026

Closes #24.

Summary

Adds Project as a named grouping of Servers. A Server belongs to
at most one Project. Projects are purely an organizational lens on
the Fleet page and a badge on Server detail. No Project page, no
aggregated views, no drag-drop in v1 (per the issue).

What landed

DomainMast.Fleet.Project + Mast.Fleet.Projects context with
case-insensitive uniqueness (citext), preset color palette
(slate, indigo, emerald, amber, rose, violet), and a Multi-based
cascade on delete that nulls servers.project_id and emits one
server.project_unassigned per affected server inside the same
transaction.

Audit events (all wired): project.created, project.renamed,
project.recolored, project.deleted, server.project_assigned,
server.project_unassigned.

UI:

  • Settings → Projects tab (list / inline create as a modal /
    rename + recolor / delete with confirm).
  • Add Server modal picks a Project, with inline "Add new project"
    that auto-selects the new project on save (mirrors the SSH key
    flow).
  • Server settings tab gets a Project card for reassignment.
  • Fleet Overview groups servers under collapsible
    ui_project_group_header rows. Servers without a Project render
    under an "Ungrouped" header when projects exist, or as a plain
    grid when none do. No "Unassigned" pseudo-group.
  • Server detail shows a color-tinted Project badge near the
    title.
  • New ui_project_group_header and ui_project_badge helpers, both
    previewed on /dev/ui.

Drive-by polish (caught during verification):

  • 4th Fleet Overview stat tile is now Releases (was Seen
    Recently) to match the Fleet Overview - Projects pen frame.
    Adds Fleet.count_all_releases/0.
  • ui_server_card rebuilt to match Card/Server in the pen (icon
    tile, name + host, status badge, OS + last-seen meta row, 3-col
    labeled CPU/MEM/DISK bars).
  • Server detail Settings → Project card got proper spacing.

Docs / process:

  • CLAUDE.md UI section now requires new custom components to be
    responsive on day one and added to /dev/ui. (We've done the
    responsive sweep twice already.)
  • CHANGELOG [Unreleased] updated.
  • Per spec, no ADR — easily reversible, no structural trade-off.

Test coverage

  • test/mast/fleet/projects_test.exs — 16 unit tests covering CRUD,
    case-insensitive uniqueness, color whitelist, cascade unassign,
    assign/unassign no-ops, audit event shape.
  • test/mast_web/live/settings_live_test.exs — 5 LiveView tests for
    the Projects tab (renders, lists with count, inline create, inline
    rename, delete).
  • test/mast_web/live/dashboard_live_test.exs — 6 tests for the Add
    Server modal Project picker (dropdown, submit with project_id,
    inline create toggle, inline create selects new) and Fleet
    grouping (group header with count + ungrouped coexistence,
    collapse toggle).
  • test/mast_web/live/server_live_test.exs — 5 tests for the
    settings tab Project field (dropdown present, assign emits audit
    event, blank value unassigns) and the header Project badge
    (renders when assigned, absent otherwise).
  • test/mast_web/live/dev_ui_live_test.exs — smoke for the two new
    component previews.

mix precommit clean: compile-warnings-as-errors, deps.unlock,
format, credo --strict, 310 tests passing.

Migration

  • priv/repo/migrations/20260523192231_create_projects.exs — enables
    citext, creates projects (binary_id, unique citext name,
    optional description/color), adds nullable servers.project_id
    with on_delete: :nilify_all + index. No backfill (per spec).

Commits

fc7fc22 fix(fleet): drop harsh border on Ungrouped section header
ee81928 fix(fleet): separate ungrouped servers with a divider + label
d3a679e fix(fleet): Releases stat tile + enriched server card to match pen
1b32041 fix(projects): modal-ize Add Project and breathe space into Server Project card
05efb55 docs(changelog): record Projects feature under Unreleased
ccca065 feat(projects): fleet grouping + Server detail Project badge
3dd144a feat(projects): wire Project into Add Server modal + Server settings
9cb71dd feat(projects): settings tab for list, create, rename, recolor, delete
7b3c022 docs(ui): require responsive new components + sync pen with Projects frames
1bc1a65 feat(projects): domain context for grouping servers

pachev added 10 commits May 23, 2026 14:32
- Migration: enable citext; create projects (binary_id, unique case-
  insensitive name, optional description/color); add nullable
  servers.project_id with on_delete: :nilify_all and index.
- Schema Mast.Fleet.Project with color preset whitelist (slate, indigo,
  emerald, amber, rose, violet), trimmed name, length validation.
- Context Mast.Fleet.Projects: list/get/create/update/delete plus
  assign_server/unassign_server. Every write goes through Multi and
  Audit.multi_log; rename and recolor each emit their own audit event
  only when the field actually changes. delete_project cascades into
  one server.project_unassigned per affected server inside the same
  transaction.
- Server schema gains belongs_to :project and accepts :project_id on
  the changeset with a foreign-key constraint.

Refs #24
…frames

- CLAUDE.md UI section: any new helper in lib/mast_web/components/ui/*
  must be responsive across the full breakpoint scale on day one, and
  added to /dev/ui for multi-width preview. Patching to responsiveness
  later is a sweep we have already done twice.
- components.pen: pulled the Projects updates from main (new frame
  'Fleet Overview - Projects' and new reusable 'ProjectGroup/Header').

Refs #24
- New tab 'Projects' on SettingsLive mirrors the SSH Keys pattern:
  inline add form, list with color swatch + server count, inline edit
  (rename + recolor + description), delete with confirm.
- ProjectsTab template extracted to MastWeb.SettingsLive.ProjectsTab so
  SettingsLive stays under the ~500 LOC house limit.
- Responsive by default: flex-wrap headers, sm: padding scales,
  min-w-0 + truncate on names, ≥40px tap targets for icon buttons,
  color picker wraps on narrow viewports.
- color_swatch/1 helper maps preset colors to bg-* classes; reused by
  Slice 4 (Fleet group header + Server badge).

Refs #24
Add Server modal (DashboardLive):
- Project select with inline 'Add new project' affordance, mirroring
  the SSH key flow. Saving a new project selects it on the server form
  automatically.
- Generalized server_form_with/3 helper (was server_form_with_key/2).

Server settings tab (ServerLive):
- New 'Project' card with a Save-on-submit dropdown. Reassignment
  routes through Projects.assign_server/2 or unassign_server/1, which
  fire the server.project_(un)assigned audit events. Blank selection
  unassigns. Same-value selection is a no-op.

Tests:
- 4 new DashboardLive tests (Project dropdown, submit with project_id,
  inline create toggle, inline create selects the new project).
- 3 new ServerLive tests (settings dropdown present, assign emits
  audit event, blank value unassigns).

Refs #24
New UI helpers in MastWeb.Components.UI.Domain:
- ui_project_group_header: chevron + colored dot + name + 'N servers'
  count. Pass a phx-click map via :toggle to make it collapsible;
  static when toggle is empty. Matches ProjectGroup/Header pen comp.
- ui_project_badge: small color-tinted chip used near the Server detail
  header and elsewhere. Title attribute reads 'Project: <name>'.
- project_color_bg/1 helper maps preset colors to bg-* classes.

Fleet page (DashboardLive):
- Servers belonging to a Project render under their group header;
  ungrouped servers render as plain cards alongside. No 'Unassigned'
  pseudo-group. Per-group expand/collapse tracked in a MapSet.

Server detail (ServerLive.Header):
- Renders the Project badge next to the page title when set.
- ServerLive loads the project on mount and re-loads it on
  update-project so the badge reflects assignment changes immediately.

DevUi (/dev/ui):
- Adds preview cards for the two new components so engineers can
  eyeball them at every breakpoint via the existing width toggle.

Tests:
- Fleet grouping: header renders with member count; ungrouped server
  still shows; no 'Unassigned' string; collapse toggle removes the
  cards.
- Server header: badge renders 'Project: <name>' when assigned; absent
  otherwise.
- DevUi smoke: both new previews appear on the page.

Refs #24
…oject card

Settings → Projects:
- Convert the inline Add Project panel to a ui_modal mirroring the
  Add Server pattern (title + subtitle, footer with Cancel + Save).
  Avoids the awkward overlap where the empty state showed under an
  open create form on the same page.

Server detail → Settings → Project card:
- Title + description block at the top, full-width select on its own
  row, Save button anchored bottom-right. The previous flex-end row
  collided the label, select, and button at row height ~32px.

Refs #24
Swap the 4th Fleet Overview stat tile from 'Seen Recently' to
'Releases' (count of all Release rows across the fleet), matching the
'Fleet Overview - Projects' pen frame. Adds Fleet.count_all_releases/0.

Rebuild ui_server_card to match Card/Server in the same pen frame:
- Server icon tile + name + host stacked top-left, status badge top-right
- Meta line with OS + last-seen relative
- CPU / MEM / DISK in a 3-col grid with labeled bars instead of stacked

DevUi sample server gets host, os_id, and last_seen_at so the preview
renders the new chrome.

Refs #24
When at least one Project group renders, the ungrouped server cards
flowed inline below the last group with no visual break, so a server
that belonged to no project looked like a member of the last group.

Add a horizontal divider with an 'Ungrouped' label and count above
the orphan grid, but only when groups are also present. Pure ungrouped
fleets (no projects assigned anywhere) keep the clean unlabeled grid.

Refs #24
The border-t divider looked heavy, especially in dark mode. Match the
chrome of ui_project_group_header (label + count, no line) so the
'Ungrouped' header reads as the same kind of section header as a
project group, just without a chevron or color dot.

Refs #24
@pachev pachev merged commit 9dd52c4 into main May 23, 2026
1 check passed
@pachev pachev deleted the claude/quizzical-faraday-c39926 branch May 23, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Projects — group Servers as an organizational lens

1 participant