feat: share the game card the way the list shows it - #157
Conversation
Share image рисовал абстрактный градиент, не имевший ничего общего с карточкой в списке игр. Теперь картинка повторяет `games/_card`: дата и время, корт, все метки (спорт, уровень, покрытие, крытый/открытый, тренировка, тренер, еженедельно, поиск игроков, погода), турнир, комментарий и счётчик мест. Заодно чиним сам рендер: Active Storage закрывает загрузчики libvips через `Vips.block_untrusted`, из-за чего svgload падал и картинка не отдавалась вовсе — ни в кнопке Share image, ни в телеграм-карточках. Загрузчик SVG открываем точечно, под мьютексом и только на время своего рендера. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 08a5d2df94
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Vips.block(SVG_LOADER, false) | ||
| begin | ||
| Vips::Image.svgload_buffer(svg, dpi: dpi) |
There was a problem hiding this comment.
Keep the SVG loader blocked for concurrent requests
In a multi-threaded web process, Vips.block changes process-wide loader state, while LOCK serializes only callers of this module. During this unblock/load window, concurrent Active Storage processing can therefore select the SVG loader for attacker-controlled input, defeating the safeguard that this code says Rails enabled; repeated public share-card requests can make that race practical. Avoid toggling the global loader during requests, or isolate trusted SVG rendering from the process handling uploads.
Useful? React with 👍 / 👎.
| body << text(text_x, y + 20, display_date.strftime("%A"), size: 15, weight: 500, color: TITLE_COLOR) if display_date | ||
| body << text(text_x, y + 38, game_time.strftime("%I:%M %p"), size: 13, color: MUTED_COLOR) if game_time |
There was a problem hiding this comment.
Use locale-aware date and time formatting
For every non-English locale passed by the controller, Ruby's raw strftime directives ignore I18n.with_locale, so the card still displays an English weekday and a 12-hour AM/PM clock. Format these values through I18n.l or locale-specific translation formats so shared cards match the user's locale.
Useful? React with 👍 / 👎.
| key = [ value, size, weight ] | ||
| cache = (Thread.current[:share_card_text_widths] ||= {}) | ||
| cache[key] ||= begin |
There was a problem hiding this comment.
Bound the per-thread text-width cache
On long-lived Puma threads, this hash retains every unique court name, comment fragment, tournament name, and badge string ever rendered. truncate_to can additionally cache every progressively shortened prefix of an overlong value, so normal traffic steadily increases worker memory and a long unbroken user value can cause disproportionate CPU and memory use in one request. Use a bounded cache or clear a request-local cache after each render.
Useful? React with 👍 / 👎.
`Vips.block` переключает состояние загрузчиков на весь процесс, а мьютекс держал только своих: в окне между «открыли» и «закрыли» соседний поток Puma мог отправить в тот же загрузчик пользовательскую картинку — ровно то, от чего Active Storage и закрывает svgload. Теперь SVG растеризует отдельный процесс, где Rails не загружается и блокировки нет, а в приложении она остаётся нетронутой. Заодно по замечаниям ревью: кэш замеров ширины живёт один рендер вместо всей жизни потока Puma, а обрезка длинной строки идёт бинарным поиском, а не посимвольным перебором с замером на каждом шаге. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Share image рисовал абстрактный градиент, не имевший ничего общего с карточкой в списке игр. Теперь картинка повторяет
games/_card: дата и время, корт, все метки (спорт, уровень, покрытие, крытый/открытый, тренировка, тренер, еженедельно, поиск игроков, погода), турнир, комментарий и счётчик мест.Заодно чиним сам рендер: Active Storage закрывает загрузчики libvips через
Vips.block_untrusted, из-за чего svgload падал и картинка не отдавалась вовсе — ни в кнопке Share image, ни в телеграм-карточках. Загрузчик SVG открываем точечно, под мьютексом и только на время своего рендера.