Upgrade Font Awesome to 6.7.2 (#4846)

* Restore the Ruby 4.0.6 upgrade

PR #4831 committed a stale Gemfile.lock over the newer one, taking
.ruby-version back to 3.2.3 and rolling back active_median, dalli,
parallel, rbs and selenium-webdriver. Dockerfile and
.devcontainer/Dockerfile still build on Ruby 4, so the Gemfile's version
check fails against the image.

Restores both files to what they were before that merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Upgrade Font Awesome from 5.15.1 to 6.7.2

Font names have moved on twice since this was pinned: the codebase still
carried Font Awesome 4 names that render as an empty box, and FA6 icons
such as fa-ellipsis-vertical were unavailable.

The 6.x gem no longer ships _font-awesome-sprockets.scss, but its
@font-face rules still call fa-font-path(), so application.scss defines
that against Sprockets' font-path helper. Its fonts live under
assets/fonts/font-awesome, which is already in the precompile list.

Icon names:
- The bare "fa" prefix is not a style in 6. Every "fa fa-<name>" becomes
  "fas fa-<name>" (38 call sites, all solid; the one brands icon already
  used fab).
- fa-sun-o was a Font Awesome 4 name and had been rendering as a box;
  now fa-sun.
- fa-pencil was likewise broken under 5, where it was fa-pencil-alt. It
  exists again in 6, so the existing name now works.
- activity_icon asked for 'fa-truck-pickup' through a helper that already
  adds the prefix, so it emitted "fa-fa-truck-pickup".

Verified that the solid, regular and brands fonts all resolve through
the asset pipeline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* couple more places to upgrade font awesome

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Brenda WallaceandClaude Opus 5 authored and GitHub committed 2026-09-24 13:39:38 +09:30
1 parent 6dbab4678f
commit 7e181982ff
21 files changed
+58 -50

No files matched your search

+5 -5
View File
@@ -141,7 +141,7 @@ export default function AddPhotoModal({label, newUrl, iconUrl, onClose, onAdded}
</div>
)}
{!loadError && !data && (
<div className="modal-body" role="status"><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
<div className="modal-body" role="status"><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
)}
{data && (
<>
@@ -161,7 +161,7 @@ export default function AddPhotoModal({label, newUrl, iconUrl, onClose, onAdded}
</button>
{waiting && (
<div className="photo-picker-waiting" role="status">
<i className="fa fa-spinner fa-spin" aria-hidden="true" />{' '}
<i className="fas fa-spinner fa-spin" aria-hidden="true" />{' '}
{blocked ? 'Your browser blocked the window.' : 'Waiting for you to allow access on Flickr…'}{' '}
{blocked && <a href={CONNECT_URL} target="_blank" rel="noopener noreferrer">Connect in a new tab</a>}
{!blocked && <span className="photo-picker-hint">This picks up on its own once you have.</span>}
@@ -216,7 +216,7 @@ export default function AddPhotoModal({label, newUrl, iconUrl, onClose, onAdded}
<>
{errors.length > 0 && (
<div className="alert alert-danger" role="alert">
<i className="fa fa-exclamation-triangle" aria-hidden="true" />{' '}
<i className="fas fa-exclamation-triangle" aria-hidden="true" />{' '}
<strong>That didn&rsquo;t work.</strong>
<ul className="mb-0">{errors.map((message) => <li key={message}>{message}</li>)}</ul>
<span>Your choice is kept, so you can press Add photo to try again.</span>
@@ -232,7 +232,7 @@ export default function AddPhotoModal({label, newUrl, iconUrl, onClose, onAdded}
<img src={chosen.preview_url} alt={chosen.title || 'The photo you chose'} className="photo-picker-preview" />
<span className="photo-picker-title">{chosen.title}</span>
<button type="button" className="btn btn-sm btn-outline-secondary ms-3" onClick={() => setChosen(null)} disabled={saving}>
<i className="fa fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> photo</span>
<i className="fas fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> photo</span>
</button>
</dd>
</dl>
@@ -245,7 +245,7 @@ export default function AddPhotoModal({label, newUrl, iconUrl, onClose, onAdded}
<div className="modal-footer">
<button type="button" className="btn btn-link" onClick={() => setChosen(null)} disabled={saving}>Back</button>
<button type="button" className="btn btn-success btn-plant" onClick={add} disabled={saving} aria-busy={saving} autoFocus>
{saving ? <><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Adding…</> : 'Add photo'}
{saving ? <><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Adding…</> : 'Add photo'}
</button>
</div>
)}
+3 -3
View File
@@ -95,7 +95,7 @@ export default function CropPicker({id, onChoose}) {
<div className="crop-picker position-relative">
<label htmlFor={id} className="crop-picker-label">What did you plant?</label>
<div className="crop-picker-field">
<i className="fa fa-search crop-picker-icon" aria-hidden="true" />
<i className="fas fa-search crop-picker-icon" aria-hidden="true" />
<input
id={id}
type="text"
@@ -113,14 +113,14 @@ export default function CropPicker({id, onChoose}) {
onChange={(event) => setTerm(event.target.value)}
onKeyDown={onKeyDown}
/>
{searching && <i className="fa fa-spinner fa-spin crop-picker-spinner" aria-hidden="true" />}
{searching && <i className="fas fa-spinner fa-spin crop-picker-spinner" aria-hidden="true" />}
</div>
<div id={`${id}-status`} className="crop-picker-hint" role="status">
{hint(searching, open, results.length)}
</div>
{noMatch && (
<div className="crop-picker-empty">
<i className="fa fa-info-circle" aria-hidden="true" />{' '}
<i className="fas fa-info-circle" aria-hidden="true" />{' '}
No crops match &ldquo;{term}&rdquo;.{' '}
<a href="/crops/new" target="_blank" rel="noopener noreferrer">Request a new crop</a>
</div>
@@ -76,7 +76,7 @@ export default function EditGardenModal({garden, onClose, onSaved}) {
</div>
)}
{!loadError && !values && (
<div className="modal-body" role="status"><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
<div className="modal-body" role="status"><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
)}
{values && (
<form onSubmit={save}>
@@ -146,7 +146,7 @@ export default function EditGardenModal({garden, onClose, onSaved}) {
</div>
<div className="modal-footer">
<button type="submit" className="btn btn-success btn-plant" disabled={saving} aria-busy={saving}>
{saving ? <><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Save garden'}
{saving ? <><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Save garden'}
</button>
</div>
</form>
@@ -91,7 +91,7 @@ export default function EditPlantingModal({planting, onClose, onSaved}) {
</div>
)}
{!loadError && !values && (
<div className="modal-body" role="status"><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
<div className="modal-body" role="status"><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
)}
{values && (
<form onSubmit={save}>
@@ -114,7 +114,7 @@ export default function EditPlantingModal({planting, onClose, onSaved}) {
<>
<strong>{values.crop.name}</strong>{' '}
<button type="button" className="btn btn-sm btn-outline-secondary" onClick={() => setChangingCrop(true)}>
<i className="fa fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> crop</span>
<i className="fas fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> crop</span>
</button>
</>
)}
@@ -183,7 +183,7 @@ export default function EditPlantingModal({planting, onClose, onSaved}) {
<div className="modal-footer">
<button type="button" className="btn btn-link" onClick={onClose} disabled={saving}>Cancel</button>
<button type="submit" className="btn btn-success" disabled={saving} aria-busy={saving}>
{saving ? <><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Save'}
{saving ? <><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Save'}
</button>
</div>
</form>
+2 -2
View File
@@ -36,14 +36,14 @@ export default function GardenCard({garden, defaultIconUrl, onPlant, highlighted
<div className="garden-card-header-actions">
{plantUrl && (
<a href={plantUrl} className="btn btn-success btn-sm garden-add-planting" onClick={plant}>
<i className="fa fa-plus" aria-hidden="true" /> Add planting
<i className="fas fa-plus" aria-hidden="true" /> Add planting
<span className="visually-hidden"> to {name}</span>
</a>
)}
<ActionsMenu
id={`garden-${id}`}
actions={actions}
label={<i className="fa fa-ellipsis-v" aria-hidden="true" />}
label={<i className="fas fa-ellipsis-v" aria-hidden="true" />}
ariaLabel={`Actions for ${name}`}
className="btn btn-sm btn-link actions-toggle-dots"
onSelect={(action, event) => {
+1 -1
View File
@@ -108,7 +108,7 @@ export default function GardenCards({gardens: initialGardens, default_icon_url:
<>
{notice && (
<div className="alert alert-success alert-dismissible" role="status">
<i className="fa fa-check-circle" aria-hidden="true" /> {notice}
<i className="fas fa-check-circle" aria-hidden="true" /> {notice}
<button type="button" className="btn-close" aria-label="Dismiss" onClick={() => setNotice(null)} />
</div>
)}
@@ -72,7 +72,7 @@ export default function MarkFinishedModal({planting, iconUrl, onClose, onSaved})
<div className="modal-body plant-dialog-body">
{errors.length > 0 && (
<div className="alert alert-danger" role="alert">
<i className="fa fa-exclamation-triangle" aria-hidden="true" />{' '}
<i className="fas fa-exclamation-triangle" aria-hidden="true" />{' '}
<strong>That didn&rsquo;t save.</strong>
<ul className="mb-0">{errors.map((message) => <li key={message}>{message}</li>)}</ul>
<span>What you entered is kept, so you can press Mark as finished to try again.</span>
@@ -118,7 +118,7 @@ export default function MarkFinishedModal({planting, iconUrl, onClose, onSaved})
</div>
<div className="modal-footer">
<button type="submit" className="btn btn-success btn-plant" disabled={saving || !finishedAt} aria-busy={saving}>
{saving ? <><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Mark as finished'}
{saving ? <><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Mark as finished'}
</button>
</div>
</form>
@@ -65,7 +65,7 @@ export default function PlantPartPicker({id, parts, suggestedId, onChoose}) {
<div className="crop-picker position-relative">
<label htmlFor={id} className="crop-picker-label">What part did you harvest?</label>
<div className="crop-picker-field">
<i className="fa fa-search crop-picker-icon" aria-hidden="true" />
<i className="fas fa-search crop-picker-icon" aria-hidden="true" />
<input
id={id}
type="text"
@@ -95,7 +95,7 @@ export default function PlantPartPicker({id, parts, suggestedId, onChoose}) {
</div>
{noMatch && (
<div className="crop-picker-empty">
<i className="fa fa-info-circle" aria-hidden="true" /> No plant parts match &ldquo;{term}&rdquo;.
<i className="fas fa-info-circle" aria-hidden="true" /> No plant parts match &ldquo;{term}&rdquo;.
</div>
)}
{showing && (
@@ -66,7 +66,7 @@ export default function PlantSomethingModal({garden, iconUrl, onClose, onCreated
<Steps steps={STEPS} current={crop ? 2 : 1} />
{errors.length > 0 && (
<div className="alert alert-danger" role="alert">
<i className="fa fa-exclamation-triangle" aria-hidden="true" />{' '}
<i className="fas fa-exclamation-triangle" aria-hidden="true" />{' '}
<strong>That didn&rsquo;t work.</strong>
<ul className="mb-0">{errors.map((message) => <li key={message}>{message}</li>)}</ul>
<span>Your choice is kept, so you can press Plant it to try again.</span>
@@ -80,7 +80,7 @@ export default function PlantSomethingModal({garden, iconUrl, onClose, onCreated
<dd className="crop-confirm-name">
{crop.name}
<button type="button" className="btn btn-sm btn-outline-secondary" onClick={() => setCrop(null)} disabled={saving}>
<i className="fa fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> crop</span>
<i className="fas fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> crop</span>
</button>
</dd>
<dt>Garden</dt>
@@ -98,7 +98,7 @@ export default function PlantSomethingModal({garden, iconUrl, onClose, onCreated
<button type="button" className="btn btn-link" onClick={onClose} disabled={saving}>Cancel</button>
{crop && (
<button ref={confirmButton} type="button" className="btn btn-success btn-plant" onClick={confirm} disabled={saving} aria-busy={saving}>
{saving ? <><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Planting…</> : 'Plant it'}
{saving ? <><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Planting…</> : 'Plant it'}
</button>
)}
</div>
+3 -3
View File
@@ -73,7 +73,7 @@ export default function PlantedDate({planting, onUpdated}) {
return (
<button type="button" className="planting-row-planted planting-date-button" onClick={open} title="Change the planted date">
{plantedAt ? `Planted ${formatDate(plantedAt)}` : 'Set planted date'}
<i className="fa fa-pencil" aria-hidden="true" />
<i className="fas fa-pencil" aria-hidden="true" />
</button>
);
}
@@ -91,10 +91,10 @@ export default function PlantedDate({planting, onUpdated}) {
onKeyDown={onKeyDown}
/>
<button type="button" className="btn btn-sm btn-link" onClick={save} disabled={saving || !value} aria-label="Save planted date">
<i className="fa fa-check" aria-hidden="true" />
<i className="fas fa-check" aria-hidden="true" />
</button>
<button type="button" className="btn btn-sm btn-link" onClick={cancel} disabled={saving} aria-label="Cancel">
<i className="fa fa-times" aria-hidden="true" />
<i className="fas fa-times" aria-hidden="true" />
</button>
{error && <small className="planting-date-error" role="alert">{error}</small>}
</span>
+1 -1
View File
@@ -17,7 +17,7 @@ export default function PlantingRow({planting, defaultIconUrl, highlighted, high
<div className="planting-row-chip">
<PlantingChip planting={planting} defaultIconUrl={defaultIconUrl} highlighted={highlighted && highlightKind === 'added'} handlers={handlers} />
{highlighted && highlightKind === 'harvested' && (
<span className="harvest-recorded-tag"><i className="fa fa-check" aria-hidden="true" /> Harvest recorded</span>
<span className="harvest-recorded-tag"><i className="fas fa-check" aria-hidden="true" /> Harvest recorded</span>
)}
</div>
<PlantedDate planting={planting} onUpdated={onUpdated} />
@@ -146,7 +146,7 @@ export default function RecordHarvestModal({planting, iconUrl, onClose, onSaved}
</div>
)}
{!loadError && !values && (
<div className="modal-body" role="status"><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
<div className="modal-body" role="status"><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
)}
{values && step === 1 && (
<div className="modal-body plant-dialog-body">
@@ -160,7 +160,7 @@ export default function RecordHarvestModal({planting, iconUrl, onClose, onSaved}
<Steps steps={STEPS} current={step} />
{errors.length > 0 && (
<div className="alert alert-danger" role="alert">
<i className="fa fa-exclamation-triangle" aria-hidden="true" />{' '}
<i className="fas fa-exclamation-triangle" aria-hidden="true" />{' '}
<strong>That didn&rsquo;t save.</strong>
<ul className="mb-0">{errors.map((message) => <li key={message}>{message}</li>)}</ul>
<span>What you entered is kept, so you can press Save harvest to try again.</span>
@@ -175,7 +175,7 @@ export default function RecordHarvestModal({planting, iconUrl, onClose, onSaved}
<dd className={step === 2 ? 'crop-confirm-name' : undefined}>
{part.name}
<button type="button" className="btn btn-sm btn-outline-secondary ms-3" onClick={() => setStep(1)} disabled={saving}>
<i className="fa fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> plant part</span>
<i className="fas fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> plant part</span>
</button>
</dd>
{step > 2 && (
@@ -184,7 +184,7 @@ export default function RecordHarvestModal({planting, iconUrl, onClose, onSaved}
<dd>
{amount()}
<button type="button" className="btn btn-sm btn-outline-secondary ms-3" onClick={() => setStep(2)} disabled={saving}>
<i className="fa fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> amount</span>
<i className="fas fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> amount</span>
</button>
</dd>
</>
@@ -195,7 +195,7 @@ export default function RecordHarvestModal({planting, iconUrl, onClose, onSaved}
<dd>
{whenLabel()}
<button type="button" className="btn btn-sm btn-outline-secondary ms-3" onClick={() => setStep(3)} disabled={saving}>
<i className="fa fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> when</span>
<i className="fas fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> when</span>
</button>
</dd>
</>
@@ -316,7 +316,7 @@ export default function RecordHarvestModal({planting, iconUrl, onClose, onSaved}
{step < STEPS.length && <button type="submit" className="btn btn-success btn-plant">Next</button>}
{step === STEPS.length && (
<button type="submit" className="btn btn-success btn-plant" disabled={saving || !harvestedAt} aria-busy={saving}>
{saving ? <><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Save harvest'}
{saving ? <><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Save harvest'}
</button>
)}
</div>
+5 -5
View File
@@ -88,7 +88,7 @@ export default function SaveSeedsModal({planting, iconUrl, onClose, onSaved}) {
const change = (toStep, what) => (
<button type="button" className="btn btn-sm btn-outline-secondary ms-3" onClick={() => setStep(toStep)} disabled={saving}>
<i className="fa fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> {what}</span>
<i className="fas fa-edit" aria-hidden="true" /> Change<span className="visually-hidden"> {what}</span>
</button>
);
@@ -100,7 +100,7 @@ export default function SaveSeedsModal({planting, iconUrl, onClose, onSaved}) {
</div>
)}
{!loadError && !values && (
<div className="modal-body" role="status"><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
<div className="modal-body" role="status"><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Loading…</div>
)}
{values && (
<form onSubmit={save}>
@@ -108,7 +108,7 @@ export default function SaveSeedsModal({planting, iconUrl, onClose, onSaved}) {
<Steps steps={STEPS} current={step} />
{errors.length > 0 && (
<div className="alert alert-danger" role="alert">
<i className="fa fa-exclamation-triangle" aria-hidden="true" />{' '}
<i className="fas fa-exclamation-triangle" aria-hidden="true" />{' '}
<strong>That didn&rsquo;t save.</strong>
<ul className="mb-0">{errors.map((message) => <li key={message}>{message}</li>)}</ul>
<span>What you entered is kept, so you can press Save seeds to try again.</span>
@@ -136,7 +136,7 @@ export default function SaveSeedsModal({planting, iconUrl, onClose, onSaved}) {
<div className="crop-picker">
<label htmlFor="save-seeds-quantity" className="crop-picker-label">About how many seeds?</label>
<div className="crop-picker-field">
<i className="fa fa-seedling crop-picker-icon" aria-hidden="true" />
<i className="fas fa-seedling crop-picker-icon" aria-hidden="true" />
<input
id="save-seeds-quantity"
type="number"
@@ -226,7 +226,7 @@ export default function SaveSeedsModal({planting, iconUrl, onClose, onSaved}) {
{step < STEPS.length && <button type="submit" className="btn btn-success btn-plant">Next</button>}
{step === STEPS.length && (
<button type="submit" className="btn btn-success btn-plant" disabled={saving} aria-busy={saving}>
{saving ? <><i className="fa fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Save seeds'}
{saving ? <><i className="fas fa-spinner fa-spin" aria-hidden="true" /> Saving…</> : 'Save seeds'}
</button>
)}
</div>
+1 -1
View File
@@ -31,7 +31,7 @@ export default function StarRating({id, label, value, onChange}) {
title={WORDS[number] ? `${number} – ${WORDS[number]}` : `${number} of 5`}
onMouseEnter={() => setHovered(number)}
>
<i className={`${number <= lit ? 'fa' : 'far'} fa-star`} aria-hidden="true" />
<i className={`${number <= lit ? 'fas' : 'far'} fa-star`} aria-hidden="true" />
<span className="visually-hidden">{number} of 5{WORDS[number] ? `, ${WORDS[number]}` : ''}</span>
</label>
</React.Fragment>
+1 -1
View File
@@ -11,7 +11,7 @@ export default function Steps({steps, current}) {
return (
<li key={label} className={`plant-step plant-step-${state}`} aria-current={state === 'current' ? 'step' : undefined}>
<span className="plant-step-number">
{state === 'done' ? <i className="fa fa-check" aria-hidden="true" /> : number}
{state === 'done' ? <i className="fas fa-check" aria-hidden="true" /> : number}
</span>
{label}
{state === 'done' && <span className="visually-hidden"> (done)</span>}