Covers flag-icons for the language switcher, a shared header layout across the choose/convert/download states, human-readable file sizes, and restyled format/quality controls and download button. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.7 KiB
File card / language switcher polish
Date: 2026-07-31
Goal
Polish pass on top of the existing frontend redesign (2026-07-31-ombrora-frontend-redesign-design): flag icons for the language switcher, a friendlier destination-format/quality picker, a consistent visual skeleton across the three file-lifecycle states (choosing format, converting, downloading), and human-readable file size shown alongside the filename in both the choosing and downloading states.
Problem
LanguageSwitcher.jsxrenders a plain text link ("English"/"Français") instead of a flag.FileConfigCard(choosing step) shows a genericFileIcon+ filename;FileCard(download step) shows only the filename with no icon — different markup between the two, which reads as the icon "disappearing" when a file moves from one list to the other.- Neither step shows file size.
- The target-format
<select>, quality slider, and download link use only default browser/plain styling.
Scope decisions (from brainstorming)
- Flags via the
flag-iconsnpm package, not emoji or hand-drawn SVG. Chosen over emoji (unreliable flag emoji rendering on some Windows/Linux font configs) and over hand-rolled SVG (user preferred pulling in the library).flag-iconsships CSS + font/SVG assets only, no native binary — safe for o2switch. Per the o2switch single-package.json constraint documented inCLAUDE.md, it must be added to bothfrontend/package.jsonand rootpackage.jsondependencies, withnpm installrun in both places. FileConfigCardandFileCardremain separate components (different responsibilities: one edits pending config, one polls job status) rather than merging into one mega-component. Instead, both adopt the same header markup shape and shared CSS classes so they render identically at the "icon + name + size" level, with only the right-hand, state-specific content differing (controls / spinner / download button / error).- Target-format picker stays a native
<select>, not a custom chip grid. Chosen for lower implementation cost and to keep native accessibility/keyboard behavior, restyled with border/focus/theme-token styling plus a small badge next to it showing the currently selected extension. - File size is computed and carried client-side, no backend change.
File.sizeis already available at selection time inHomePage.handleFilesSelected. For the download step,uploadFiles(items)returns jobs in the same order as the request array (confirmed insrc/app.js'sPOST /api/jobshandler, which builds jobs by iteratingreq.filesindex-for-index) — sohandleConvertcan zip the returned jobs with the originalvalidItemsby index to attachsizeonto each job object before it's stored insubmittedJobs, with no API contract change. - "Converting" state gets a spinning icon, not a progress bar (no real progress percentage exists to drive one) —
CircleNotchfrom the already-installed@phosphor-icons/react, animated via a CSS@keyframes spin, respectingprefers-reduced-motion(existing design-system rule from the foundation spec). - Download link becomes a filled button matching
.convert-button's visual language (same accent color, radius, min-height), not just a restyled inline link.
Architecture
New dependency
Add flag-icons to dependencies in both package.json (root) and frontend/package.json, then npm install in both locations to keep both lockfiles in sync (per existing o2switch mirroring rule).
New util
frontend/src/utils/formatBytes.js — pure function formatBytes(bytes) returning a human-readable string ("2.4 MB", "512 KB", "0 B" for falsy/zero/undefined input). Used by both FileConfigCard and FileCard.
Shared header markup/CSS
Both components render the same header shape:
<div class="file-tile-header">
<FileIcon />
<span class="file-tile-name">{name}</span>
<span class="file-tile-size">{formatBytes(size)}</span>
</div>
CSS classes .file-tile-header, .file-tile-name, .file-tile-size added to frontend/src/styles/home.css (renaming/superseding today's .file-config-name, which only exists in that file today and is used by both components already — checked: FileCard.jsx currently reuses .file-config-name for its filename span, so this is a rename/extension of an existing shared class, not a net-new pattern). FileCard gains the icon it's currently missing.
FileConfigCard.jsx changes
- Add size display next to filename (via
item.file.size). - Restyle the target-format
<select>(new.format-selectclass: themed border, focus ring, padding) and add a small.format-badgespan next to it showingitem.targetFormat.toUpperCase(). - Quality slider, PNG compression slider, ICO size select, PDF checkbox: consistent spacing/label styling under a shared
.file-config-controlsrefinement — no logic changes, styling only.
FileCard.jsx changes
- Add
FileIconto the header (parity withFileConfigCard). - Add size display — needs
sizepassed as a new prop fromHomePage. - "Converting" state:
CircleNotchicon (spin animation) next to the existing translated text. - "Done" state: download link restyled as a filled button (new
.download-buttonclass reusing.convert-button's visual tokens), keeping the existing<a href={downloadUrl(jobId)}>semantics.
HomePage.jsx changes
handleConvert: afteruploadFiles(validItems)resolves, zip the returnedjobsarray withvalidItemsby index to attachsize: validItems[i].file.sizeonto each job object before appending tosubmittedJobs.- Pass
size={job.size}down toFileCard.
LanguageSwitcher.jsx changes
- Import
flag-icons/css/flag-icons.min.cssonce (inmain.jsx, alongside other global CSS imports — checked:main.jsxcurrently imports./index.css, matching the existing pattern for global stylesheet imports). - Replace the text
labelwith<span class={fi fi-${otherLang === 'en' ? 'gb' : 'fr'}} />(flag-icons usesgbfor the English/UK flag, noten— there's no ISO country code "en"), keeping anaria-label/titleset to the existing translated label (t('nav.switchToEnglish')/t('nav.switchToFrench')) for accessibility, since a flag alone doesn't convey the target language to screen readers.
Out of scope
- Replacing the native
<select>with a custom chip/grid picker (considered, declined — see scope decisions). - Any backend/API changes — file size is entirely a client-side, already-available value.
- A progress-percentage-driven progress bar for the "converting" state (no real progress data exists).
- Automated frontend tests (no test harness exists yet, per the foundation spec's own out-of-scope note).
Testing
Manual only, consistent with the foundation spec's approach (no frontend test harness exists):
npm run devinfrontend/, both/fr/and/en/.- Upload one or more files, verify: file icon + name + size shown in the choosing step; format select is restyled with the extension badge visible; quality/PNG/ICO/PDF controls still function exactly as before.
- Convert, verify: converting state shows the spinning icon; on completion the download step shows the same icon + name + size layout as the choosing step, plus a filled download button; clicking it downloads the converted file.
- Verify the language switcher shows the correct flag for the other language (FR page shows GB flag and vice versa) and that keyboard/screen-reader users still get a meaningful accessible name.
- Verify at 375/768/1024/1440px widths that the new header layout doesn't break wrapping (existing
flex-wrapbehavior in.file-config-card/.file-cardshould still apply). - Verify dark mode: flag icons, badges, and the new button render with sufficient contrast in both themes.