Casadesign - DRAFT DO NOT MERGE#7051
Draft
giacoelho wants to merge 174 commits into
Draft
Conversation
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>
giacoelho
requested review from
FireLemons,
compwron and
elasticspoon
as code owners
July 9, 2026 23:01
giacoelho
marked this pull request as draft
July 9, 2026 23:01
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>
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>
Full-suite run surfaced 15 reds; 13 fixed here: - devise/passwords/new (8): stale devise-default labels — "Forgot your password?" -> "Forgot password?", "Send me reset password instructions" -> "Send reset instructions", "Change my password" -> "Update password", "Log In" -> "Sign in"; dropped the old Bootstrap "N errors prohibited" header assertion (the casa_auth page renders an inline error list; the error text itself is still asserted). - banner_helper (2): #conditionally_add_hidden_class returns Tailwind "hidden" now, not Bootstrap "d-none". - volunteers/edit impersonate (1): the banner shows formatted_name (honorific stripped, per the design rule), so gave the supervisor an explicit honorific-free display_name instead of the Faker default. - casa_cases/index (1): retired the obsolete test of the removed Bootstrap "#ddmenu_my-cases" sidebar dropdown (the casa_app nav is flat links). - learning_hours/volunteers/show (1): a coupled spec my round-2 sentence-case sweep missed — it still asserted "'s Learning Hours"; also dropped the stale "spec-locked" comment. Left (pre-existing, unrelated to this work): truncated_text_component preview spec (Lookbook preview JS) and timeout_warning (window.timeout isn't wired into casa_app) — reported separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o 180) The Bootstrap->casa_app migration dropped the inline script that set window.timeout, so src/session_timeout_poller.js always fell back to its `|| 180` default and ignored the user's configured timeout_in. Re-render it in casa_app's head, before the deferred bundle, from @timeout_duration (= current_user.timeout_in, already set by ApplicationController#set_timeout_duration). Unit is minutes: the poller does `timeout * 60 * 1000` and the jest test sets window.timeout in minutes, so render `.in_minutes.to_i` (180 for a 3h timeout), `.to_json` for erb-safe script interpolation. Fixed timeout_warning_spec, which asserted `.in_seconds` (10800) — that never matched the minutes the poller uses. (all_casa_admin has the same latent pattern but resolves to the same global config.timeout_in = 3.hours it already defaults to, so no behavior change there.) erb_lint clean; spec green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The truncated_text preview spec failed because ViewComponent previews rendered without the JS bundle, so the truncated-text Stimulus controller never connected and "read more" did nothing. Add a component_preview layout that loads the application bundle + tailwind, and point ViewComponent at it via `config.view_component.previews.default_layout` (the 4.x key -- not the `default_preview_layout` guess). That surfaced a real component bug: TruncatedTextComponent + its controller used Bootstrap `d-none` / `text-bold` / `d-inline`, which don't exist in the Tailwind build -- so on the case-contact card (a live page) the [read more]/[hide] swap was silently broken (both showed; the toggle no-op'd). Converted to `hidden` / `font-bold`. Spec green; jest 133 green (the read_more/case_button_states jest tests exercise different code); builds clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record that the full suite was last at 3451 examples / 15 failures (all fixed, verified in isolation) and note to re-run it end-to-end to confirm 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o ops Move the platform activity charts off the public /health page and into the authenticated all-CASA console, closing a cross-org exposure: the charts query every chapter's data, but /health is public and unauthenticated. - Extract HealthController's chart math into a MetricsReport query object that scopes to the whole platform by default, or to one CasaOrg when given (CaseContact via its dedicated `casa_org` scope; LoginActivity via the joined users.casa_org_id). This is the shared engine for the new Metrics/Analytics pages. Cross-org isolation verified (A=3, B=1, global=4). - AllCasaAdmins::MetricsController#index renders the platform charts behind the existing all-CASA auth, nav'd as "Metrics" in the console sidebar. - Shared metrics/_dashboard partial (range filter + 3 charts) so the upcoming per-chapter Analytics page reuses identical chart math. - Rename HealthHelper -> MetricsHelper (its home is now Metrics/Analytics) and parameterize the range filter's base path. - Slim /health to what it's for: a minimal, self-contained ops status page (HTML) plus the latest_deploy_time JSON. Drop the unused legacy JSON analytics endpoints and retire the now-dead metrics layout + JS bundle. Charts render via the metric_* helpers + chart-hover controller, both already in the application bundle, so no separate JS bundle is needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New AnalyticsController#index renders the same three activity charts as the all-CASA Metrics console, but MetricsReport is scoped to current_organization so each chapter sees only its own data. Gated by AnalyticsPolicy (admins and supervisors, matching the Reports audience); nav'd as "Analytics" beside Reports in the casa_app sidebar. Specs: AnalyticsPolicy (admin/supervisor allowed, volunteer denied), a request spec (roles + signed-out redirect + range preset), and a MetricsReport unit spec asserting cross-org isolation (org A sees 3, org B sees 1, unscoped sees 4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three headline numbers above the charts, scoped to the signed-in chapter: contacts this month (with a month-over-month delta badge), active volunteers, and cases needing contact. Reuses AdminDashboard for the volunteer/needs-contact figures (the app's canonical 14-day "no recent contact" definition, batched for org scale) and adds contacts_this_month / contacts_previous_month to MetricsReport for the delta. Metrics (all-CASA) stays charts-only; KPIs are chapter-actionable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The KPI cards had drifted from the documented "KPI stat card" pattern (missing the semantic icon tile, wrong label/meta colors, an ad-hoc amber number and a bespoke delta pill). Reconcile them to the exact token used by the dashboard cards: icon tile -> text-3xl slate-900 number -> text-sm slate-500 label -> text-xs slate-500 meta; "cases needing contact" now mirrors the dashboard's amber attention treatment; the contact delta is a colored meta line (arrow + signed number + "vs last month", never color-only). Document the Metrics/Analytics feature in design.md (Charts now built in MetricsHelper and rendered on Metrics/Analytics; KPI stat card variants; a Migration status entry) and design-todo.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rack-test system specs covering the sidebar nav → page flow: an all-CASA admin reaches Metrics and sees the platform charts; an admin reaches Analytics and sees the KPI cards + charts; a supervisor can reach it; a volunteer sees no Analytics nav link. (Charts are server-rendered SVG, so no JS driver is needed.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Found by the full-suite run: truncated_text_component_spec still asserted `span.text-bold`, but the component was migrated to `span.font-bold` (the earlier d-none->hidden / text-bold->font-bold change) without updating the spec. Update both assertions to match the rendered class. Also record the full-suite result in the backlog (3470 examples; the other 4 reds are headless-chrome flakes that pass in isolation). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3470 examples; 1 real stale-spec red fixed (truncated-text), the other 4 are headless-chrome flakes that pass in isolation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…alytics pages Converge the page vertical rhythm on index/list pages so the filter->table and section gaps match instead of drifting page to page: - volunteers: drop the reserved `min-h-[2.5rem]` band under the filter (a persistent ~68px empty gap above the table when nothing is selected). Put `mb-4` on the Manage button so the gap collapses to 16px, matching the cases index; the button still reveals on selection and pushes the table down only then. - supervisors: filter `mb-5` -> `mb-4` (16px), matching the other rosters. - analytics/metrics: range filter gains `mt-6` (it butted flush at 0px under the KPI row / subtitle); chart sections 2 & 3 `mt-5` -> `mt-6` so the whole dashboard reads as one uniform 24px column. - design.md: document the page vertical rhythm and the anti-patterns. Pixel-verified over CDP: filter->table = 16px on volunteers / cases / supervisors / reimbursements; analytics above-filter = 24px, section gaps = 24/24. Green: volunteers, supervisors, analytics, metrics system specs + axe (volunteers/supervisors/reimbursements); standardrb + erb_lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ged emoji The case-contact card put a 36px medium-type icon in a full-height gutter (`flex items-start`), indenting the title, secondary text, answers and notes ~48px right of the card edge. Move the icon into the header row inline with the title (smaller 32px `h-8 w-8` tile) so the body spans the card's full width, flush-left to the `p-5` edge — industry standard: a leading icon adorns the header, it is not a body gutter. Also drop the decorative transition-aged emoji from the case-number heading (`display_case_number` returns the plain case number). It's data-as-emoji, which design.md already bans; no other view renders it. Pixel-verified (CDP): body indent 48px -> 0; icon 36 -> 32px; heading "CINA-08-1001" (emoji gone). Green: case_contacts index system spec + decorator spec (20 examples) + axe; standardrb + erb_lint clean. design.md: documented the leading-icon card-header pattern. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed title) TruncatedTextComponent (the case-contact answer/notes detail lines) hard-coded a `font-bold` label and set no font size, so it inherited 16px — making the detail labels 16px/700, heavier AND bigger than the card's own 16px/600 title. The detail shouted and the title whispered. Align it to design.md's fact/detail-list convention: `text-sm` container, a muted `font-medium text-slate-500` label, a regular `text-slate-800` value, and a real `brand-600` `[read more]` link. Now the title (16px / 600 / slate-900) is the sole anchor and every supporting/detail line is `text-sm`. Pixel-verified (computed style): answer label 16px/700 -> 14px/500/slate-500; value 16px -> 14px; read-more 16px/plain-text -> 14px/500/brand-600. Green: truncated_text component + system specs + case_contacts index (19 examples); erb_lint + standardrb clean. Updated the component label spec + design.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(kill read-more spam) Every topic answer and the notes were each their own line-clamped TruncatedTextComponent with a separate "[read more]", so reading a single contact took several clicks (one per line). Replace them with ONE native <details> "Show details" toggle per card that reveals the full answers + notes at once as a dl (muted `text-xs font-semibold` dt / dark `whitespace-pre-line` dd) -- matching the sibling new-design table's expandable detail panel. Industry standard: detail is progressively disclosed with a single per-card control, not fragmented behind N micro-toggles. Specs: the two :js tests that pinned the old truncation are updated -- the "contact notes" test now drives the single Show-details expand; the case-filter test asserts on the always-visible section case numbers / an empty list rather than collapsed notes. Full case_contacts index spec (16) + axe green; erb_lint + standardrb clean. design.md: documented the disclosure pattern and answered the divider question. Note: TruncatedTextComponent is now unused (left in place; safe to delete later). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…a-card) + sentence-case default contact types
The "Show details" panel boxed topic answers + notes in their own rounded-xl
border card *inside* the case-contact card -- a card-in-a-card, which isn't a
pattern in the app. Replace the box with a `border-t border-slate-100 pt-3`
divider on the <details> (matching metric_data_table's "View as table"
disclosure) and render the dl unboxed.
Separately, default contact types still displayed Title Case ("Fictive Kin",
"Social Services") though DEFAULT_CONTACT_TYPE_GROUPS is already sentence-cased:
generate_for_org! find_or_creates by name and never renames, so orgs seeded
before the constant change kept the old names. Add the missing after_party task
to rename them -- matching only case-variants of a shipped default so
org-renamed / custom names are left alone.
WCAG: the disclosure is a native <details>/<summary> (keyboard + screen-reader
expanded state), aria-hidden chevron, AA-contrast summary + labels; axe green.
Verified: case_contacts index spec (16) + axe green; erb_lint + standardrb
clean; ran the task on dev (renamed 3 groups + 39 types to sentence case).
design.md: documented the nested-card divider rule + constant-needs-migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Only conflict was app/views/case_contacts/_case_contact.html.erb: main (#7055) added a reimbursement status badge to the Bootstrap card while casadesign had rewritten the card in Tailwind. Kept the casadesign design and folded the new feature in as a casadesign pill (amber = pending, emerald = complete) using the merged reimbursement_status_text / _badge_type decorator methods, so main's new specs (card shows "Reimbursement Pending"/"Complete") still pass. Everything else auto-merged: dependency bumps (caxlsx 4.5, friendly_id 5.7, strong_migrations 2.8, fontawesome 7.3, etc.) alongside casadesign's axe/Tailwind deps; reimbursement controller + specs; decorator (medium_icon + reimbursement_*). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tack topic notes Three UX fixes on the case-contact form: - Relevant case(s): on edit (@case_contact.active?) the model requires draft_case_ids and the picker only ever offered the single case the contact belongs to, so the removable multiselect only let the user dismiss a required, fixed parent. Render it read-only (static chip(s) + hidden draft_case_ids); keep the editable TomSelect multiselect only for new / draft contacts. - Topic answers: replace the 50/50 grid (topic | squeezed notes) with a stacked row — topic select on top, full-width notes textarea below, since the note is the substance and deserves the width. - Delete: the standalone danger_outline button becomes a subtle ghost remove (design-system "recede for repeated-row actions"), keeping the visible "Delete" text + confirmDestroyAndRemove hook the spec drives. All Stimulus/DOM hooks preserved. Green: contact_topic_answers / edit / new / additional_expenses system specs (2 new_spec failures were Chrome-crash flakes, pass in isolation); erb_lint clean. design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…emove topic" Notes section discussion topics: - Each topic was a rounded-lg bordered box inside the Notes card (a card-in-a-card). Replace with a border-t divider between topics (no box) — separates them without nesting cards. - The "Delete" button read as "clear the field". Relabel to "Remove topic" (ghost, top-right of the topic) so it's clear it drops the whole entry; keep the confirmDestroyAndRemove hook. Updated the one spec that clicked "Delete". Still open (recommended separately): the per-row topic dropdown disables already-used topics, so it reads as redundant against the fixed 6-topic set — a fixed-topic questionnaire (a textarea per topic) is the better navigation, but that's an autosave / reject_if / JS / spec rewrite. Green: contact_topic_answers system spec (11); erb_lint + standardrb clean. design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…re the dropdown) Replace the "Add another discussion topic -> row with a dropdown (others disabled) -> per-row Delete" nested-form with a fixed-topic checklist: every org topic is a divider-separated row (checkbox + question) whose full-width notes textarea reveals on check. No dropdown (it was redundant against a fixed topic set), no nested cards, and unchecking is a clear "remove". - New `contact-topics` Stimulus controller: create the ContactTopicAnswer on check (POST /contact_topic_answers, store the id), destroy on uncheck (confirm if notes exist); the 2s autosave then only *updates* the value via that id. - CaseContact rejects id-less topic-answer attrs (reject_if: id blank) so a slow autosave can't create a duplicate; prepare_form no longer seeds a blank answer (it orphaned a nil-topic row). Checkbox label is associated via `for` (wrapping double-fires change -> double create). - case-contact-form's topic-select/add-button code is now inert (already guarded by hasContactTopicSelectTarget); the reimbursement toggle is unaffected. Specs: rewrote the topic-answer system spec + shared helper to the checklist; updated the new/edit specs (no dropdown / add button) and the form request spec (the wizard update now only *updates* topic answers -- creation is the /contact_topic_answers endpoint, already spec'd). Green: contact_topic_answers / new / edit / additional_expenses system specs (54), form request spec (53), case_contact model + case_contacts request (94); erb_lint + standardrb + standardjs clean. design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…de the collapsible form) The reimbursement section's autosave status line (an `invisible` <small>) sat at the card's top level, so when reimbursement isn't requested (form collapsed) it reserved a blank line + space-y gap -- ~40px of dead space that made the empty card look over-padded next to the other cards (all the same p-6). Move the status line inside the collapsible reimbursement form so the empty state is just heading + checkbox (measured card height 158 -> 138px). Autosave feedback still shows once the form is expanded. design.md: documented the collapsible-section padding rule, and that volunteer_address is a single free-text string (Address#content, shared with the volunteer profile + reimbursement exports) -- so structured address fields are a cross-cutting Address-model change, not a tweak. Verified: additional_expenses + edit system specs (16) green; erb_lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/ state / zip) Replace the single free-text address field with structured parts on the volunteer profile edit (users/edit) and the admin's volunteer edit (volunteers/edit). The Address model keeps `content` as the canonical composed one-line value every reader already uses (reimbursement table, mileage CSV export, case-contact prefill): compose_content syncs it from the parts on save and only runs when a structured field is present, so legacy content-only rows and the factory are untouched. New nullable columns (Strong Migrations safe); an after_party task seeds line_1 from legacy content (a line_1-only address composes back to exactly line_1, so readers are unaffected). Params permit the new attrs alongside :content for backward compatibility. This is the canonical-Address (profile) layer. The reimbursement-card address is a separate follow-up: it's a per-contact snapshot string with a two-way profile sync and an autosave contract, so structuring it safely is its own change. Verified: address_spec (compose/structured), users request spec (incl. content backward-compat), volunteers/edit address system spec -- all green; standardrb + erb_lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ne 2 / city / state / zip) The reimbursement card's single free-text "Mailing address" becomes structured fields, matching the volunteer profile. They are five virtual attrs on CaseContact that compose into the existing volunteer_address snapshot string (Address.compose, shared with Address#content), so storage, the reimbursement validation, exports, and readers are unchanged. compose_volunteer_address is skipped when all parts are nil, preserving the legacy single-string path (request specs, factory) and yielding "" on a blank submit so the reimbursement-wanted validation still fires. Fields prefill from the volunteer's structured Address (decorator#volunteer_address_parts); each is a volunteerAddress Stimulus target so clearMileage empties all five on uncheck; the ambiguous multi-volunteer state disables them as before. On submit, update_volunteer_address syncs the structured parts back to the volunteer's profile Address (legacy content path retained). Specs target the line-1 field (a line-1-only compose == the old single value, so the "123 Example St" expectations hold). Verified: form request + case_contact/address/decorator model specs (163) green; new (reimbursement section), edit, additional_expenses system specs (25) green; standardrb, standardjs, erb_lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + clearer contact-type recency On edit, the required case moves from a read-only chip in a form cell to a badge beside the "Details" heading (it's fixed context, not an editable field), and the now-lone date field is left-aligned instead of sitting in a half-empty 2-col grid. New/draft contacts keep the TomSelect multiselect + date grid. The #draft-case-id-selector id and hidden draft_case_ids are preserved for the JS/spec contract. The unlabeled "never" / "3 days ago" next to each contact type was the last time that type was logged for this case — confusing as a bare word. It now reads "Last logged 3 days ago" and is omitted entirely when never logged. ContactTypeDecorator#last_logged_hint_with_cases (nil for never) backs the form; #last_time_used_with_cases is unchanged for the multiselect subtext. Verified: contact_type_decorator (8), new (28) + edit (8) system specs green; standardrb + erb_lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… not window.confirm
Unchecking a discussion topic that still has notes triggered a browser-default confirm
("Remove this topic and its notes?"). It now opens the native <dialog> confirmation, matching
the shipped court-order remove pattern: contact-topics keeps the topic checked, opens its dialog
target via showModal(), and removes the pending topic on confirm (Cancel / Escape / backdrop keep
it). Confirm/Cancel use the Dialog:: Header/Body/Footer suite + button_classes.
Verified: contact_topic_answers system spec (11, incl. the confirm path + a new cancel path)
green; standardjs + erb_lint clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e card padding - Deleting a SAVED additional expense now opens the native <dialog> confirmation (court-order pattern: casa-nested-form opens its dialog target via showModal and deletes on confirm); brand-new unsaved rows still remove without a prompt. Removed the dead window.confirm confirmDestroyAndRemove. - Additional-expense rows are border-t dividers instead of grey bg-slate-50 cards, reusing the standard input style so they match the rest of the form (all .nested-form-wrapper / data hooks kept). - Autosave status lines toggle display (hidden/block) instead of visibility (invisible), so an idle Notes/Reimbursement card reserves no trailing line and its bottom padding matches the Details card. (autosave is used only on this form, so the change is scoped.) Verified: additional_expenses (9, incl. confirm + new cancel), contact_topic_answers (11, autosave "Saved!"), edit "autosaves notes" — green; standardjs + erb_lint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e fields + trash Delete) The divider treatment read poorly and left the Delete button flush against the next line. Replace it with the design-system nested-row pattern from casa_cases/_court_order_fields: a bordered card (rounded-lg border-slate-200 p-3, no grey fill) that lays amount / details / a trash "Delete" inline (sm:flex-row sm:items-start), with rows spaced by space-y-3. Fields use aria-label + placeholder instead of stacked labels, matching the court-order row. All JS hooks (.nested-form-wrapper, .expense-*-input, .remove-expense-button, data-*, hidden id/_destroy) are preserved. Verified: additional_expenses system spec (9) green; erb_lint clean. design.md note corrected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ount/description/Delete Per review: go back to a divider between expenses (border-t border-slate-200 + symmetric py-4, so Delete no longer sits flush against the next divider), restore the visible field labels, and stack the fields vertically — amount, then description (required), then a right-aligned Delete below — instead of the inline bordered-card layout. All JS hooks (.nested-form-wrapper, .expense-*-input, .remove-expense-button, data-*, hidden id/_destroy) preserved. Verified: additional_expenses system spec (9) green; erb_lint clean. design.md note updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t a ghost-button row Replace the full-width danger_outline "Delete" ghost button (its own row below the fields, which pushed content down and added whitespace) with a compact trash icon button pinned to the top-right of each expense row (flex items-start, h-8 w-8, rose hover). Fields stay stacked with visible labels in the left column. Specs click the .remove-expense-button hook (the button is icon-only now). The saved-expense confirm dialog is unchanged. Verified: expense remove / cancel / add system specs green (in isolation; the full-file run has the known Chrome-under-load flake on an unrelated add example); erb_lint clean. design.md updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a work in progress. Will flag when it is ready to merge.