useTheme's initial state now always renders 'light' on both server and
client's first render pass, matching exactly. The real stored/OS-preferred
theme is applied afterward in an effect (runs only post-hydration), instead
of computing it during the initializer, which returned different values
between the server (no window) and a client whose actual localStorage/
matchMedia preference differed from the SSR default (e.g. dark-mode OS or
a stored 'dark' preference) — a real hydration mismatch on the ThemeToggle's
rendered icon and aria-pressed attribute, intermittent per visitor/system.
Since the vite-react-ssg migration replaced the dynamic /:lang route param
with literal /fr and /en routes, useParams().lang always returned undefined
here, breaking the header brand link (linked to /undefined/) and silently
breaking the language switcher (always resolved otherLang to 'fr').
Fixes SeoHead (title/canonical/hreflang/JSON-LD) to render during SSR via
vite-react-ssg's Head component instead of a client-only useEffect, so
crawlers see real tags without executing JS. Also fixes an SSR crash in
useTheme (localStorage access with no window during server render) and
removes now-duplicated static meta tags from index.html.
Applies prisma/migrations/*/migration.sql on first container boot via a
sourced docker-entrypoint-initdb.d script, so a fresh container matches a
`prisma migrate deploy` run without needing shadow-database privileges.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Verified exact file-type v22 magic-byte detection for all 5 formats against
real fixture buffers (no normalizeFormat aliases needed), and simplified the
converter's resolution-scaling gate to match audio.js's validation-boundary
pattern (app.js range-checks, converter trusts the value).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every other family (images, documents, fonts, ebooks) had a showcase
pair in the marquee; archives was missing one after the archive
conversion feature landed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Also adds a --color-family-archives CSS variable (light + dark) that
the plan didn't call out explicitly but is needed for the new family
tile/icon to render with a color instead of falling back silently.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Also fixes a third double-extension bug the plan missed: app.js derived
each job's uuid via path.basename(file.filename, path.extname(...)),
which only strips the last dot segment. For a compound extension like
tar.gz this left "<uuid>.tar" as the "uuid", overflowing the uuid
column (Char(36)) and crashing the request. Replaced with
stripExtension(). Also bumped rateLimitMaxJobs in jobs.test.js's config
override — the file's cumulative POST /api/jobs calls across all tests
now exceeds the production default (20/window) once the archive tests
are included, causing spurious connection resets independent of any
real bug.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
archiver v8 (installed) uses a class-based API (`ZipArchive`) rather than
the factory-function API most examples online still describe — adjusted
createZip() to match after the first test run surfaced the mismatch.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets users drop an uploaded file from the UI list before or after
conversion, without cancelling anything server-side.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rebuild the visual identity from the actual Ombrora mark (violet/cyan aurora
blobs) instead of the generic blue/amber scheme, add Bricolage Grotesque and
IBM Plex Mono for display and code-like text, and give the hero an animated
aurora backdrop, gradient headline, and a format-pair marquee driven by the
real registered converters.
Rework the file config form: target format and icon size are both chip
groups now instead of a native select, and quality/compression sliders get a
gradient-filled track, custom thumb, and a mono value pill to match. File
type icons are color-coded by family (images/documents/fonts/ebooks) and
reused across FileConfigCard, FileCard, and FormatsGrid.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The download button was showing the input file's size (carried over
client-side from upload) instead of the actual converted file's size.
It now reads outputSizeBytes from the polled job status once done.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Explains the react-router-dom resolution failure encountered deploying
the frontend redesign, and the fix (mirror frontend runtime deps into
root package.json) so it doesn't need re-discovering next time.
The o2switch nodevenv/Passenger setup only installs a single
package.json/node_modules (root), so npm install --prefix frontend
never actually populates frontend/node_modules there. Node's module
resolution walks up from frontend/src to the root node_modules, so
adding react-router-dom, react-i18next, i18next, and
@phosphor-icons/react to root's dependencies (react/react-dom were
already present) lets the production vite build resolve them without
needing a second node_modules tree.
Migrates the existing upload/format-select/quality/convert logic from
the old single-file App.jsx into a router shell (/ -> /fr//en/) with a
shared Layout (header, language switcher, dark-mode toggle, footer).
Tool logic itself is unchanged, only restyled into cards.
Part of the Ombrora Convert redesign (Task 2/5).
Foundation sub-project: merged homepage+tool page, FR/EN i18n via URL
prefix, dark mode, SEO basics, and reassurance elements backed by
actual backend guarantees (1h auto-delete, 100MB max, no account).
Also fixes a real bug found via the fb2 API test: file-type sniffs a
real fb2 file's XML declaration as generic "xml", not "fb2" and not
undetected, so it never reached the undetectable-format fallback added
in the previous commit. Added an fb2->xml alias in normalizeFormat,
same pattern as the existing azw3->mobi one.
Verified empirically: fontkit has no default ESM export in this project's
"type": "module" setup (import fontkit from 'fontkit' throws). The working
form is import * as fontkit from 'fontkit'.
Empirically verified library choices (fonteditor-core, opentype.js, fontkit)
against real fonts before committing to the scope, and root-caused a
file-type/dfont MIME collision that would otherwise silently break uploads.
9 tasks covering mime.js, the ico/heic converter modules, a new
iconSize column on ConversionJob, app.js/worker.js wiring, and the
frontend size picker. Every code snippet (icojs encode/decode, the
heic-convert ESM import, prisma db execute/migrate status flags) was
verified against the actual installed/resolved packages rather than
guessed.
Reuses libheif's own LGPL/MIT-licensed with-alpha-512x512.heic sample
under both a .heic and .heif filename, verified to decode correctly
via heic-convert (the library the image format work will use). No
suitably-licensed HEVC/mif1-branded .heif-distinct sample exists that
is compatible with the pinned libheif-js version, and file-type detects
both extensions identically regardless of container brand, so the
duplicate content is sufficient to exercise the heif source-format
code path.