Skip to content

Casadesign - DRAFT DO NOT MERGE#7051

Draft
giacoelho wants to merge 233 commits into
mainfrom
casadesign
Draft

Casadesign - DRAFT DO NOT MERGE#7051
giacoelho wants to merge 233 commits into
mainfrom
casadesign

Conversation

@giacoelho

Copy link
Copy Markdown
Collaborator

This is a work in progress. Will flag when it is ready to merge.

gia and others added 12 commits July 9, 2026 17:49
Introduce Tailwind v4 alongside the existing Bootstrap stack so the UI can be
modernized page-by-page without breaking current views.

- Add tailwindcss + @tailwindcss/cli (v4), build:tailwind[:dev] npm scripts,
  and a `tw` process in Procfile.dev. Output lands in app/assets/builds, which
  Sprockets already serves via the manifest's link_directory.
- Add app/assets/stylesheets/tailwind.css: CSS-first design system (Inter font
  + an indigo "brand" palette) — the visual direction for the redesign.
- New Tailwind-only layout casa_auth.html.erb (split brand panel + form),
  used by both the user and all-casa sign-in controllers.
- Redesign devise/sessions/new.html.erb in a clean, modern Stripe/Airbnb style
  and wire up remember-me properly.

Scoped to sign-in only; every other page keeps the Bootstrap `application`
layout untouched. First step of an incremental migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Approved font direction: swap the design-system typeface and the auth
layout's webfont link from Inter to Figtree (warmer humanist sans).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- design.md: living design-system doc (Tailwind v4 approach, Figtree type scale,
  color tokens, icon-tile pattern, component + empty-state specs, app-shell spec,
  migration checklist). Codifies the icon-tile treatment (icons on a soft colored
  background) for stat/status icons and reserves initial-avatars for people only.
- CLAUDE.md: point to design.md and record the casadesign commit/push-per-checkpoint
  workflow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New Tailwind-only layout: left sidebar with Pundit-guarded nav mirroring the
existing routes, plus a top bar (notifications, profile menu, sign out). Coexists
with the Bootstrap `application` layout — redesigned authenticated pages opt in via
`layout "casa_app"`. Icons via Bootstrap Icons CDN + Figtree (per design.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Supervisors now land on a real dashboard (DashboardController#show renders it in the
casa_app shell) instead of the Bootstrap volunteers table:

- SupervisorDashboard service builds per-volunteer follow-up status + summary stats
  from the supervisor's assigned volunteers.
- View: KPI cards, a "Needs your attention" list (icon-tile treatment per design
  feedback — replaces the ringed avatars), and a roster with color-coded
  contact-status pills.
- Empty states handled: no volunteers -> welcome + CTAs; nobody behind -> all
  caught up. The full volunteer management table remains at /volunteers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Feedback from design review:
- Avatar initials strip honorific prefixes (Mrs./Mr./…) and use first + last name
  only, via a shared `avatar_initials` helper (removed the duplicate in the service).
- Top-bar profile menu is triggered by the avatar alone; the name lives in the
  dropdown, not the trigger.
- Page header is the page name ("Dashboard") + <title>, not a greeting.

Accessibility (WCAG AA targets):
- Skip-to-content link, landmark labels, aria-labels on icon-only controls,
  aria-current on the active nav item, table scope + sr-only caption, visible
  focus rings, contrast bumps (slate-400 -> 500/600), role=status/alert on flash.

Responsive:
- Sidebar becomes an off-canvas drawer below lg with an accessible toggle
  (button + aria-expanded, Escape/backdrop to close) and a responsive grid/padding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Names now render as first + last only (no Mr./Mrs./...) wherever a person is shown
in the redesign — the shell sidebar/profile menu and the supervisor dashboard
(names and aria-labels), plus the avatar initials.

- New `NamePresentation.strip_honorific` + a `display_person(user)` view helper.
- Presentation-only: the stored `display_name` is left untouched. (An attempted
  `User#display_name` override was reverted — a security spec requires display_name
  to round-trip raw input unchanged, e.g. dangerous strings.)
- Documented in design.md + CLAUDE.md so it's applied on every page going forward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bottom-of-sidebar user block (avatar + name + role) duplicated the identity
already shown by the top-right avatar account menu, and was non-interactive. Drop
it; the top-right menu is the single source for identity + account actions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "CV" placeholder brand mark wasn't a value-add at 36px (the org name already
identifies the chapter) and using the real org logo there would add image/variant
infrastructure for little gain. Show the org name alone in the sidebar header; the
full logo stays for contexts with room (sign-in, court reports). design.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the rest of the auth flow in line with the redesigned sign-in:
- Forgot password (passwords/new), reset password (passwords/edit), and
  accept-invitation (invitations/edit) rebuilt in Tailwind, matching the sign-in's
  inputs/buttons/error styling. CASA's email-or-phone reset is preserved.
- Users::PasswordsController uses `layout "casa_auth"`; the invitation-accept action
  renders in casa_auth (admin invite `new`/`create` untouched).
- casa_auth `<title>` is now driven by `content_for(:page_title)`; each page sets its
  own title (Sign in / Reset your password / Choose a new password / Accept your invitation).

Verified all three render 200 in the new layout (accept-invitation via a valid token).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply the honorific-free naming rule to the existing Bootstrap UI, not just the
redesign:
- 37 `.display_name` person-name sites across ~20 legacy views now render via the
  `formatted_name` helper (org names, form-value symbols, and the checkbox-id slug
  line were intentionally excluded).
- The 4 DataTables (volunteer, supervisor, case-contact, reimbursement) strip
  honorifics on their Ruby name outputs (display_name + supervisor_name); SQL
  select/order strings are untouched.
- Added a `formatted_name(name)` string helper alongside `display_person`, both
  backed by `NamePresentation`.

Verified /supervisors, /volunteers, /casa_cases render 200 with zero honorifics;
datatable classes load clean; standardrb + rendering green. Docs updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The header helper text becomes a short single sentence with no em-dash; the org
name already appears in the sidebar header, so it's no longer repeated here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added dependencies Touches dependency files javascript Touches JavaScript code ruby Touches Ruby code erb Touches ERB templates labels Jul 9, 2026
@giacoelho
giacoelho marked this pull request as draft July 9, 2026 23:01
gia and others added 3 commits July 10, 2026 15:34
DashboardController#show now renders the casadesign supervisor dashboard
(SupervisorDashboard + casa_app layout) for supervisors, but the request
spec still asserted the old redirect to the volunteers overview and was
failing on the branch. Assert the dashboard renders and lists an assigned
volunteer instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…app shell

First page-by-page migration off the legacy Bootstrap UI, following design.md.
The notifications page is linked straight from the new shell (top-bar bell +
account menu), so it was the most visible remaining Bootstrap seam.

- Render NotificationsController#index in the casa_app layout.
- Restyle the index, NotificationComponent, and patch-notes partial onto the
  design system: card list, leading icon tiles, unread dot, and a cold-start
  empty state. Switch Font Awesome (fas fa-*) to Bootstrap Icons (bi-*), which
  is the icon set actually loaded on Tailwind pages.
- Move the specs off Bootstrap-class selectors onto semantic data-* hooks
  (data-notification-list-item, data-read / data-unread-dot) without weakening
  behavior coverage. All notification/patch-note ordering, counts, and empty
  states remain asserted.

Verified: component, view, request (renders the full casa_app layout), and
system specs all pass.

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

- design.md: reframe as the permanent source of truth to refer to for all UI
  work. Add a "Design decisions (rationale)" log capturing the why behind the
  system (Tailwind-alongside-Bootstrap, layout opt-in, indigo/slate, Figtree,
  bi-* icons, icon-tile-vs-avatar, org-name-only sidebar, presentation-only
  honorific stripping, triage dashboards, empty-state taxonomy, a11y bar,
  build/output), a repeatable "Migrating a page" playbook, and a migration
  status that points at the backlog.
- design-todo.md: new prioritized, phased backlog (the "what's left") for the
  page-by-page migration off Bootstrap, plus cross-cutting infra and known
  pre-existing debt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the 🧪 Tests Tests label Jul 10, 2026
gia and others added 6 commits July 10, 2026 15:54
Second app-shell leaf page (linked from the account menu). Rebuilds the whole
edit-profile screen on the design system while preserving every behavior.

- Render UsersController in the casa_app layout.
- Rewrite edit.html.erb into cards: details, account, security, communication
  preferences, plus the volunteer languages table — using the documented input,
  button, and checkbox patterns.
- Replace the Bootstrap collapse accordions (Change Password / Change Email)
  with a small `disclosure` Stimulus controller (real <button> trigger, toggles
  a hidden panel, syncs aria-expanded) instead of relying on Bootstrap JS.
- Add a reusable Tailwind `shared/_form_errors` partial (the legacy
  `shared/_error_messages` stays for Bootstrap pages); it keeps the
  `@custom_error_header` hook so "... this password change ..." still renders.
- Preserve all field labels, ids, and JS-hook classes
  (toggle-email/sms-notifications, save-preference, password-new/-confirmation,
  submit-password, current_password_email, toggle-sms-notification-event) so
  require_communication_preference.js and password_confirmation.js keep working.

Verified: users request spec + the full edit system spec pass (79 examples);
erb-lint, standardrb, and StandardJS clean; JS bundle builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rofile done

Add the two components introduced by the edit-profile migration (the Tailwind
form-error summary and the disclosure/collapsible-panel pattern) to design.md,
and tick edit profile in both the design.md status and design-todo.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rd subtitle

- Add a role badge (current_role as a brand-tinted pill) to the casa_app account
  menu header, under name + email — the one place identity metadata lives, and a
  parity with the role line the legacy header used to show. Industry-standard
  placement for a multi-role internal tool (keeps the sidebar/top-bar chrome clean).
- Supervisor dashboard subtitle -> "Track volunteer progress at a glance."
- Document the role badge in design.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The person-badge icon added no signal, so remove it. Colour the pill by role
using the design system's non-semantic accent tints — Volunteer = sky (blue),
Supervisor = violet (purple), Casa Admin = amber — so roles read apart at a
glance; unknown roles fall back to slate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pt WCAG AA

- Drop the shouty `uppercase` transform from all design-system labels (account
  details, dashboard + languages table headers, patch-note category, auth "or"
  divider); render them sentence case. Sentence-case isolated copy ("My
  languages", "Learning hours this year").
- Bump muted label text from slate-400 (fails AA) to slate-500/600 for contrast.
- Redesign the form-error summary (shared/_form_errors): a role="alert" card with
  a leading icon and a tidy bulleted list instead of the plain block; keeps the
  heading text + #error_explanation so specs and behavior are unchanged.
- design.md: add Sentence case + Accessibility (WCAG 2.1 AA) as standing
  conventions, and fix the muted-text token. design-todo: track the app-wide
  interactive-label sentence-case pass and a slate-400 contrast audit.

Interactive button/field labels shared across pages (Update Profile, Change
Password, field labels) are spec-coupled across users/volunteers/all-casa-admin
and a shared example, so they're deferred to a holistic pass (tracked).

Verified: 109 examples across the touched pages pass; erb-lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per design feedback: one sentence, no bullets, dashboard-style icon tile.
- shared/_form_errors now renders "Unable to save: <messages via to_sentence>"
  inside a rose danger icon tile (bg-rose-100 text-rose-600, bi-exclamation-
  triangle) matching the dashboard, dropping the old heading + bulleted list.
- Update edit_spec assertions from the Rails "N errors prohibited this X from
  being saved:" text to "Unable to save".
- design.md: refresh the Form errors component note.

Verified: 79 users edit + request examples pass; erb-lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gia and others added 30 commits July 23, 2026 15:38
…orific strip)

The #supervisor_volunteer_supervisor_id options render honorific-free names (formatted_name /
NamePresentation.strip_honorific), but the spec compared against the raw display_name. Faker::Name
sometimes generates a "Rev."/"Dr." prefix, so the assertion passed or failed depending on the seed --
the pre-existing rspec (and docker) CI failure. Compare option text against the stripped names so it
matches the view deterministically. Non-system suite now 2925 examples, 0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…below content)

Pagination was placed three different ways: inside the table card (learning-hours), below with an
mt-4 wrapper (volunteers/casa_cases/case_contacts), and below bare (reimbursements) -- so the divider
and spacing differed per page.

Industry standard: a footer bar directly below the results -- "Showing X-Y of Z" left, page controls
right, a top divider, edge-aligned to the content. Responsive pages hide the desktop table card on
mobile, so pagination can't live inside it; the one placement that works everywhere is below the
content. shared/_pagination now owns that (mt-4 gap + border-t + pt-4, edge-aligned, no horizontal
inset), and every call site renders it bare -- dropped the mt-2/mt-4 wrappers and moved the
learning-hours copy out of its card.

Verified identical on volunteers / reimbursements / learning-hours (16px gap, left-aligned to the
content edge, 1px divider, 16px padding). View specs 6/0; erb_lint clean; design.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he below-card bar)

Reviewer feedback: the below-card bar (mt-4 gap + divider + pt-4) required too much scrolling; use the
industry-standard in-table footer (like learning-hours). So:
- shared/_pagination is back to a compact in-card footer (border-t + px-4 + pt-3, no external gap).
- learning-hours pagination moved back INSIDE its card.
- Responsive pages (reimbursements / volunteers / casa_cases): pagination now renders INSIDE the
  desktop table card as its footer, plus a `md:hidden` copy below the mobile card list (the desktop
  card is `hidden md:block`, so an in-card-only render would vanish on mobile).

Verified headless: desktop shows exactly ONE pagination, inside the table card at its bottom
(learning-hours / reimbursements / volunteers / cases); mobile shows the md:hidden copy below the card
list. View + system specs 33/0; erb_lint clean; design.md guidance corrected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eimbursements card py-2

The in-card footer used pt-3 with no bottom padding, so the page numbers sat ~8-9px from the card
bottom -- and ~1px on reimbursements, whose desktop table card was missing the py-2 inset the other
table cards have -- so it read as cramped. Footer is now symmetric py-3, and the reimbursements table
card gets py-2 to match.

Verified headless: all four table pages identical -- footer padTop/padBottom 12px, ~21px under the
page numbers (was ~8-9). View specs 3/0; erb_lint clean; design.md updated (pt-3 -> py-3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, not py-2)

The footer was symmetric (py-3), but the table card's py-2 inset added 8px BELOW the footer (under
pb-3), so the page numbers sat ~12px from the divider but ~20px from the card's bottom edge -- top
padding < bottom. Change the four paginated table cards from py-2 to pt-2 (keep the top inset for the
table, drop the bottom one) so the footer's py-3 is the only padding around the numbers.

Verified headless: 13px above == 13px below the numbers on learning-hours / reimbursements /
volunteers / cases. View specs 6/0; erb_lint clean. Documented in design.md (a footered table card
uses pt-2, not py-2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ame links (supervisors index)

The "Volunteers without supervisors" table was a read-only list whose only action was an unlabeled
name link to the volunteer's edit page -- it read as purposeless. Make it a worklist:

- Inline per-row "Assign supervisor": a supervisor <select> (@active_supervisors) + Assign button
  posting to supervisor_volunteers#create (the same endpoint the volunteer edit page uses; it
  redirects back via request.referer, so the volunteer drops off the list on success). Verified
  end-to-end -- assigning removes the volunteer from the list.
- Make the volunteer name obviously clickable: dark identifying text (the design system keeps person
  names dark, not brand links) with a PERSISTENT underline -- a non-color cue (WCAG 1.4.1), slate-800
  on white is 14.7:1 (1.4.3), + a focus ring (2.4.7). Was hover-only underline, which read as plain
  text. design.md Names guidance updated.

supervisors index + supervisor_volunteers specs 26/0; erb_lint + standardrb clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e_link_class helper

Sweep the hover-only-underline person-name (and dark in-text) links to a PERSISTENT underline so
they're discoverably clickable -- WCAG 1.4.1 (non-color cue), slate-800 on white 14.7:1 (1.4.3), focus
ring (2.4.7). Add a `name_link_class` helper (matching the button_classes pattern) and point every dark
name link at it: learning-hours (2), supervisors (roster + _manage_volunteers + unassigned + assigned-
to names), volunteers, casa_admins, casa_cases (show + _volunteer_assignment), other_duties. Callers
prepend the font size/weight (e.g. "font-medium #{name_link_class}").

Brand-colored entity links (case numbers, court dates) are unchanged. Verified: helper classes render
on /volunteers + /supervisors with no interpolation leak; swept-page system specs 79/0; erb_lint +
standardrb clean; design.md Names guidance points at the helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ss + document both link patterns

The volunteer + admin dashboards rendered case-number (record) links in DARK (text-slate-800) --
inconsistent with the brand-colored case-number links everywhere else. Add a `record_link_class` helper
(brand-600 + hover underline + focus ring, the counterpart to name_link_class) and reconcile the 4
dashboard case-number links to it.

design.md now documents the two (and only two) in-content link treatments: dark `name_link_class` for
people, brand `record_link_class` for records (case number / court date), with the WCAG rationale --
brand color is the cue in a links-only cell; add a persistent underline for a record link that sits
inline in body text (brand-on-text is under 3:1).

Verified: volunteer dashboard renders 9 brand case-number links, 0 dark; dashboard spec 4/0; erb_lint
+ standardrb clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(match row-action pattern)

The inline "Assign supervisor" column read wrong: left-aligned, and Assign was a primary CTA --
over-emphasized for a repeated per-row action and inconsistent with every other table's row buttons.
Match the pattern:
- Right-align the column: header text-right + cell text-right + the form flex justify-end (the form's
  right edge now sits at the cell's content edge, filling the column like the other action columns).
- Assign button -> the tertiary ghost (bi-person-plus + "Assign"), same style as Edit/Detail view/
  Impersonate app-wide, not a filled CTA. Still type=submit (behavior unchanged).

design.md's Tertiary (ghost) note now states that EVERY table row action -- including a form-submit
like Assign -- uses the ghost (never a primary/secondary CTA), and the whole trailing column is
right-aligned.

Verified headless: header/form right-aligned (form right edge at the 16px cell padding), button bg
transparent + slate-600 (ghost, not primary). supervisors index + assign-endpoint specs 26/0; erb_lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…), not colored text links

The casa_org settings tables were the drift the previous confirmation claimed was fixed but wasn't:
Edit was a brand-colored text link and Delete a rose text link, unlike every other table's tertiary-
ghost row actions. Centralize the ghost in a `ghost_class(:neutral|:danger)` helper (single source of
truth, Tailwind-scannable literals -- the same reason button_classes exists) and convert both:
- Edit   -> ghost_class + bi-pencil (matches Edit / Detail view / Impersonate app-wide)
- Delete -> ghost_class(:danger) + bi-trash via the confirm dialog (matches case_groups)

20 Edit links + 4 Delete triggers converted; 0 old-style remain. Pixel-verified: Edit bg transparent /
slate-600 / bi-pencil, Delete bg transparent / rose-600 / bi-trash, both 28px; no 375px overflow.
erb_lint + standardrb clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ss helper (single source)

Follow-up to the settings-table fix: eliminate the remaining copy-pasted ghost strings so the tertiary
ghost has ONE definition and can't drift again.
- 4 views (other_duties, supervisors, volunteers, casa_admins) dropped their inline `ghost` local and
  now call `ghost_class` -- byte-identical output, pure refactor.
- case_groups dropped its `action_link` local; Edit -> ghost_class, Delete -> ghost_class(:danger).
  This also normalizes its buttons from the outlier `px-2.5 py-1.5` to the canonical `px-2 py-1`.
- design.md's Tertiary (ghost) note now documents the helper + its :neutral/:danger variants and says
  to call the helper, never hand-write the string.

All pages render 200 with the ghost classes present and 0 errors; erb_lint clean. ghost_class output
was pixel-verified on the settings page (neutral: transparent/slate-600/28px; danger: transparent/
rose-600/28px), and every migrated view emits that same helper string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… helper (app-wide sweep)

The app-wide audit (the step I skipped before) turned up more row-action drift beyond casa_org:
- Edit brand-text links -> ghost_class + bi-pencil: mileage_rates, hearing_types checklist items,
  all_casa_admins/casa_orgs/show.
- Delete rose-text links -> ghost_class(:danger) + bi-trash (behavior/confirm preserved):
  users/_languages, hearing_types checklist items (x2).
- Hand-rolled slate-ghost class strings -> ghost_class: casa_cases/index (Edit, desktop + mobile),
  supervisors/index (mobile cases-card Edit). These looked right but bypassed the helper.

0 old-style row actions remain in the touched files; erb_lint clean; pages render 200 with 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ashboard View, remove-expense, filter toggles)

Finishing the app-wide ghost sweep so ghost_class is the ONLY definition:
- dashboard/supervisor "View" row action: was a brand-text link -> ghost_class + bi-eye (recedes like
  Detail view elsewhere; sits beside the Send reminder button as a proper tertiary).
- shared/_additional_expense_form Remove: was a hand-rolled slate ghost missing inline-flex ->
  ghost_class (JS hook + aria-label preserved). design.md already specified this as the slate ghost.
- case_contacts + case_contacts_new_design filter toggles: ghost-shaped toolbar toggles at the outlier
  px-2.5 py-1.5 -> ghost_class (px-2 py-1); data-action/aria/content preserved.

Full app-wide audit now reports 0 in every drift category (brand/rose text actions, hand-rolled ghost
strings, inline ghost locals). Only the distinct grid h-8 w-8 icon-button in _actions remains (a
separate component). erb_lint clean; pages render 200 with 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sign.md line 333)

Reported: the settings Delete button was red. design.md already decided the destructive tertiary ghost
is "slate, not the old rose that read as too jarring" (the remove-expense decision) -- I contradicted
it by inventing a ghost_class(:danger) rose variant (following case_groups' OLD rose precedent + the
generic delete=red convention) and missed the note.

- ghost_class collapsed to slate-only, no variant, so rose can't be reintroduced.
- 9 delete call sites (casa_org contact_topics/custom_org_links, case_groups, hearing_types checklist
  items, users languages) -> the slate ghost. The bi-trash icon + "Delete" label + confirm dialog
  carry the danger, not the color. Prominent destructive CTAs stay button_classes(:danger_outline).
- design.md's ghost note corrected: one slate ink, delete included; never rose.

Pixel-verified: settings Delete now slate-600 (identical ink to Edit), transparent bg. The only rose
left on that page is the user-menu "Sign out" link (intentional nav). delete-flow specs 41/0; helper
standardrb + erb_lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…est) -- industry standard

Reported: the destructive hover was inconsistent (gray in some tables, rose in others). My prior fix
over-corrected to all-gray, which strips the danger affordance (Delete then looks like Edit). The
industry standard for a repeated destructive action is neutral-at-rest + danger-on-hover (GitHub,
Gmail, Linear): it avoids the jarring wall of red (design.md line 333) AND signals danger at the
point of action. So: slate at rest (your first point) + rose on hover (your consistency point).

- ghost_class(:neutral | :danger): BOTH slate at rest; :neutral hovers gray, :danger hovers rose.
  The variant is back, but correct this time -- rose on HOVER, never at rest.
- Every destructive row action -> :danger (rose hover), consistently: casa_org contact_topics /
  custom_org_links, case_groups, hearing_types checklist items, user languages, remove-expense, and
  the case-contact delete icon (aligned to the same rose-700 hover).
- Edit / Detail view / View / Assign / filters stay :neutral (gray hover).
- design.md (ghost note + line 333) updated to the slate-at-rest + rose-on-hover standard.

Verified: Delete carries hover:bg-rose-50 hover:text-rose-700 (12) and Edit hover:bg-slate-100 (128);
all three hover rules compiled in tailwind.css (gated in @media (hover:hover), which is why headless
forcePseudoState can't render them). Delete-flow specs 41/0; erb_lint + standardrb clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…NT + "Volunteer since"

The "Assigned cases" column was a link-list that was usually empty (case and supervisor assignments are
independent, but unsupervised volunteers tend to be new/caseless) and whose links pulled the user out
of the assign flow. Replace it with more useful triage data:
- "Cases": a compact count ("1 case" / "3 cases", em-dash for none). When >0 it's the real signal --
  an unsupervised volunteer carrying cases is a live oversight gap. Scannable, stays in the flow.
- "Volunteer since": join date (I18n :full), always populated -- the urgency signal (how long they've
  waited for a supervisor).
Controller preloads casa_cases (.includes) so the count is N+1-free.

Verified via CDP: heads Volunteer / Cases / Volunteer since / Assign; rows show "1 case" (seeded) and
em-dash for 0-case volunteers; dates render. supervisors/index spec 14/0; erb_lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… supervisors"

The assign dropdown lists EVERY active supervisor in the org -- unbounded, and large CASA chapters run
dozens to 100+ -- so a native <select> repeated per row doesn't scale. Convert it to the existing
searchable-select TomSelect (type-ahead), reused from the court-report case picker.
- searchable_select_controller: add a configurable dropdownParent (default null -> court-report
  unchanged). The assign picker sits inside the table's overflow-x-auto + overflow-hidden card, so it
  passes dropdown-parent-value="body" and the menu renders on <body> instead of being clipped.
- Minimal fallback class (block w-full): the tom-select theme owns .ts-control's border/padding, and
  TomSelect copies the select's classes to .ts-wrapper, so a bordered class double-borders. Dropped
  the manual chevron (the .ts-wrapper::after caret handles it).
- design.md: documented the searchable single-select pattern (when to type-ahead + the
  dropdownParent/overflow + minimal-fallback caveats).

Verified via CDP: enhanced (3 wrappers), 8 options, search "Alice" -> ["Alice Nguyen"], "Zzzz" -> 0,
dropdown on <body> (not clipped), no double border (ctrl 1 / wrap 0), selection updates the underlying
value. court-report + supervisors index + assign-endpoint specs 44/0; erb_lint + standardjs clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ty state, clear x, disabled-until-picked

Follow-up on the type-ahead's affordance (it read as a plain preselected dropdown: no hint it was
searchable, no empty state, no clear, unexplained default):
- Loads BLANK with a "Search supervisors…" placeholder (searchable-select placeholder-value) -- signals
  it's typeable, is the empty state, and removes the confusing auto-selected first supervisor. A leading
  blank <option> defaults the native select to empty; hidden from the menu (.option[data-value=""]).
- Assign is DISABLED until a supervisor is picked (searchable-select toggle-submit-value disables the
  form's submit; re-disables on clear), so you can't assign no one; + disabled: styles on the button.
- CLEAR (x): clear_button plugin; the x replaces the chevron on focus/hover (hide ::after then) so they
  never overlap (casa doesn't load the bundled bootstrap clear-button theme).
- design.md: documented all of it under the searchable single-select pattern.

Verified via CDP: blank+placeholder on load; button disabled -> enabled on pick -> disabled on clear;
x shows on focus/hover with the chevron hidden, idle shows the chevron with x hidden; empty option
hidden; search filters; menu on <body>. court-report + supervisors + assign specs 44/0; erb_lint +
standardjs clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, clear search affordance

Follow-up on the picker looking off (uneven padding, too wide, not obviously a type-ahead):
- Padding: the single-select content wasn't vertically centered -- the selected item stretched to the
  full content box so the text rode high (empty was 7/17). align-items:center on the single .ts-control
  (+ align-self:center on the item/input) -> symmetric 12/12 in both empty and selected states.
- Width: 208px (w-52) was too wide -> w-48 (192px).
- Search affordance: a leading magnifier (base64-SVG background on .ts-wrapper.single .ts-control +
  padding-left) + a clearer "Search supervisors" placeholder in the design-standard slate-400, so it
  reads as a type-ahead search field.

Verified via CDP + darkest-pixel: icon painted (0.39 = slate-500 magnifier), placeholder fits + is
slate-400, both states symmetric (12/12), width 192px, item sits after the icon. design.md documents
the search-field look. court-report + supervisors index specs 32/0; erb_lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-align the caret

Reported: the "Search supervisors" copy wasn't visible and the caret sat ~1/3 in. Root cause: the blank
<option value=""> plus allowEmptyOption:true made TomSelect treat "" as a SELECTED item -- it hid the
input (and its placeholder) off-screen (left:-10000px) and showed an empty item, so the field read as
blank with the caret pushed inward.

Fix: a placeholder picker now sets allowEmptyOption:false (in searchable_select, keyed off
placeholder-value so the court-report picker is unchanged). The empty option is no longer an item, so
the input stays on-screen showing the placeholder and the caret sits at 33px -- right after the search
icon (left-aligned).

Verified via CDP + pixel read: control children are just input + clear-button (no empty item), input
on-screen at 33px, value empty + Assign disabled, placeholder paints (10% dark text pixels in the copy
region). court-report + supervisors specs 32/0; standardjs clean. design.md notes the gotcha.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The design.md edit for the type-ahead fix (3154e6e) failed to apply due to a malformed editor
payload, so the code shipped without the doc. Record it: a placeholder picker must set
allowEmptyOption:false, else the blank <option> becomes a selected item and TomSelect hides the input +
placeholder off-screen (the "invisible copy / caret pushed ~1/3 in" bug).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eft-aligned

Reported: clicking the field put the caret ~1/3 in, and making a selection dropped it to a 2nd line
(growing the field) -- abnormal for a single-select. Cause: TomSelect's input carries
`min-width: 7rem !important`, so a selected name + the input exceed the ~128px usable width of the 192px
field and wrap under flex-wrap:wrap.

Fix (scoped to .ts-wrapper.single): flex-wrap:nowrap + input min-width:0 + item truncation
(overflow/ellipsis). The item + input stay on one line (input shrinks, long names ellipsize) and the
caret sits at 33px -- right after the search icon.

Verified via CDP: empty-focused caret at 33px, control 42px; selected+focused stays 42px (no wrap) even
with a 45-char stress name (the item ellipsizes). court-report + supervisors specs 32/0. design.md
documents the one-line rule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aret left on focus, icon gone on select)

Reported: the caret still wasn't left-aligned on click, and the search icon lingered after a selection --
neither is standard type-ahead behavior, and the icon-after-select wasn't the direction given. Cause:
the magnifier was on .ts-wrapper.single always, so its padding-left offset the caret on focus and the
icon stayed once a value was chosen.

Fix: scope the icon to .ts-wrapper.single:not(.focus):not(.has-items) -- shown ONLY when empty and
unfocused. On focus (typing) or with a value selected it's a plain field: no icon, caret/text at the
normal left (~10px, the control's base padding).

Verified via CDP -- resting empty: icon, content at 33px; focused empty: no icon, caret at 11px (left);
selected: no icon, name at 11px. All stay one line (42px). court-report + supervisors specs 32/0.
design.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g (px-3.5 / 14px)

Follow-up: align the single-select's non-icon left padding to the design-system text inputs (px-3.5 =
14px) rather than the ts-control's 10px base, so the caret/text lines up with the other inputs.
`.ts-wrapper.single .ts-control { padding-left: 0.875rem !important }` (the resting-empty icon state
still overrides to 2rem). Verified: focused + selected caret at 15px (14px padding), resting-empty icon
at 32px, all one line (42px).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ement mapping

A trackable map of the Bootstrap -> Tailwind migration, built by inspecting the code (not just the
design.md checklist): every page-rendering controller resolves to a Tailwind shell (casa_app /
casa_auth / all_casa_admin / own), and a Bootstrap-marker scan found only dead (0-render) legacy
files, mailers, and false positives -- no live Bootstrap page remains. Includes the shells, per-area
page coverage, the element/pattern mapping (card / button / DataTable / modal / form / select / flash /
dropdown / names / links), and the loose ends (dead files to delete, mailers out of scope by ADR 0007,
the help-link item, /health ops page).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…the migration map

Verified 0 references across views / rb / js / specs before deleting (the "still used by legacy
court-date pages" comment on _court_order_list was stale -- court dates render the casa_app twin
casa_cases/_court_orders). Deleted:
- shared/_court_order_form + _court_order_list (Bootstrap court-order sub-form; twin is casa_cases/_court_orders)
- casa_cases/_thank_you_modal + case_contacts/_confirm_note_content_dialog (legacy Bootstrap modals; Dialog:: replaced them)
- layouts/_mobile_navbar (legacy nav; casa_app has its own shell)
- devise/shared/_links + all_casa_admins/shared/_links (legacy auth link partials)

Kept (my first map was wrong): shared/_emancipation_link is LIVE (rendered by CasaCaseDecorator) but
still legacy-styled (main-btn primary-btn btn-sm) -> a migration item, not a deletion.
layouts/footers/_logged_in is app-dead but covered by footer.html.erb_spec.rb -> deleting it needs that
spec removed too (deferred). MIGRATION-MAP.md loose-ends corrected accordingly.

Smoke specs (court_dates/new, sessions/new, case_contacts/new) 46/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e migration map

The footer partials (layouts/footers/_logged_in + _not_logged_in) render in no layout -- only
spec/views/layouts/footer.html.erb_spec.rb exercised them. Deleted all three. Their links (Ruby For
Good / Report a site issue / SMS Terms & Conditions) survive on the all_casa_admin layout footer + the
static landing; casa_app itself has no footer (a pre-existing gap, noted in the map).

Also corrected the map's emancipation note: _emancipation_link is rendered by
CasaCaseDecorator#transition_aged_youth, which no view calls (only the decorator spec) -- the migrated
cases index built its own Yes/No transition column and dropped the quick-link. So it's dead code, not a
live element; left pending a delete-vs-re-add decision rather than migrating styling no one sees.

Decorator + cases-index specs 18/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d but unreachable)

Reported as a serious miss: admin / mileage rates / banners / imports appeared "missing". They were
migrated (Tailwind, casa_app) but linked NOWHERE in the casa_app nav -- reachable only by typing the
URL. A full reachability sweep confirmed 6 orphaned pages; this wires the 4 admin ones into a new
"Administration" section on the Settings page (Settings is admin-only, so no per-link gating needed).
Verified: the 4 links render and /casa_admins, /mileage_rates, /banners, /imports all return 200.

MIGRATION-MAP.md corrected: my earlier "all migrated" claim checked Tailwind-on-a-shell, not
navigability -- that was the real miss. Remaining orphans recorded: other_duties (conditional volunteer
feature) and emancipation_checklists#index (index-only), placement TBD.

casa_org/edit spec: only the pre-existing Twilio :63 flake fails (unrelated, parked); the additive
section broke nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pation checklists) -- 0 orphans left

Closes out the reachability sweep from the previous commit. other_duties: now a sidebar item (gated by
policy(OtherDuty).index?, which already includes the org's other_duties_enabled toggle) + @active_nav in
the controller so the sidebar highlights. emancipation_checklists#index: a volunteer-only link in the
Cases page header (see_emancipation_checklist? is volunteers-only, so it can't live in the
admin/supervisor More menu).

Verified role-gated: admin sees "Other duties" in the sidebar but not the volunteer-only emancipation
link (/other_duties 200); volunteer sees both, and /emancipation_checklists reaches the page (302 ->
redirect to their single transitioning case, per the controller). other_duties + cases-index specs 4/0.

All 6 orphaned pages are now linked; MIGRATION-MAP.md updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…is the point)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

db Touches database schema or migrations dependencies Touches dependency files erb Touches ERB templates javascript Touches JavaScript code pundit Touches Pundit authorization policies ruby Touches Ruby code 🧪 Tests Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants