fix(landing): sharp в образе через NODE_PATH; итерация критика 3 — переносы

Next 16 грузит sharp обычным require и NEXT_SHARP_PATH не читает: без sharp
/_next/image отдавал исходный JPEG. Критик 3 (9/10): точка-разделитель
нот и «·» в скобке недели не висят в начале/конце строки, имя и фамилия
латиницей не разрываются.
This commit is contained in:
2026-10-17 17:13:36 +03:00
parent 397cf62a37
commit b662e89058
6 changed files with 11 additions and 5 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ export function Label({ item, roastDate, wide = false }: { item: Item; roastDate
<div className={wide ? 'lg:px-8' : 'flex flex-col'}>
{item.notes.length > 0 && (
<Row label={label.notes} wide={wide}>
<span className="tag text-ink">{item.notes.map(keep).join(' •\u00A0')}</span>
<span className="tag text-ink">{item.notes.map(keep).join('\u00A0• ')}</span>
</Row>
)}
{item.spec && (
+1 -1
View File
@@ -38,7 +38,7 @@ export function WeekStrip({ week }: { week: RoastWeek }) {
<span className="week-bracket-line" aria-hidden="true" />
<div className="week-bracket-note">
<p className="tag text-cherry">
{cupNote.day} · {cupNote.label}
{cupNote.day}{'\u00A0·\u00A0'}{cupNote.label}
</p>
<p className="text-small text-ink">{typo(cupNote.caption)}</p>
</div>
+2 -1
View File
@@ -17,7 +17,8 @@ describe('typo', () => {
expect(typo('Иргачеффе вкусный')).toBe('Иргачеффе вкусный');
});
it('неразрывный дефис в фамилии', () => {
expect(typo('Scott Soltys-Curry')).toBe('Scott Soltys‑Curry');
expect(typo('Scott Soltys-Curry')).toBe('Scott\u00A0Soltys\u2011Curry');
expect(typo('Фото: Mona Jain, Tina Guina')).toBe('Фото: Mona\u00A0Jain, Tina\u00A0Guina');
});
it('слеш не уходит на новую строку', () => {
expect(typo('Curry / Unsplash')).toBe(`Curry${N}/ Unsplash`);
+3
View File
@@ -5,6 +5,8 @@ const SHORT = 'а|в|во|и|к|ко|на|не|но|о|об|от|до|по|с|с
const SHORT_RE = new RegExp(`(^|[\\s(«—–-])(${SHORT}) `, 'giu');
const UNIT_RE = /(\d) (кг|г|шт|BYN|мин|дн\.?|день|дня)(?=$|[\s.,:;!?)»])/gu;
const NAME_HYPHEN_RE = /([A-Z][a-z]+)-([A-Z][a-z]+)/g;
// имя и фамилия латиницей не разрываются: «Scott Soltys-Curry»
const NAME_PAIR_RE = /\b([A-Z][a-z]+) (?=[A-Z][a-z]+)/g;
export function typo(text: string): string {
// дважды: «и в Минске» — после замены первого слова второе стоит уже после NBSP
@@ -13,6 +15,7 @@ export function typo(text: string): string {
return out
.replace(UNIT_RE, `$1${NBSP}$2`)
.replace(NAME_HYPHEN_RE, '$1\u2011$2')
.replace(NAME_PAIR_RE, `$1${NBSP}`)
.replace(/ \//g, `${NBSP}/`);
}
+3 -2
View File
@@ -23,7 +23,8 @@ RUN pnpm --filter @kvarta/landing build \
&& mkdir -p apps/landing/public \
&& rm -rf apps/landing/.next/standalone/node_modules/.pnpm/@img+sharp-*
# sharp для next/image под целевую платформу; npm — чистый JS, под эмуляцией работает
# sharp для next/image под целевую платформу; npm — чистый JS, под эмуляцией работает.
# Next 16 грузит его обычным require("sharp") (NEXT_SHARP_PATH больше не читает) — находит через NODE_PATH.
FROM node:22-alpine AS sharp
WORKDIR /sharp
RUN npm install --omit=dev --no-audit --no-fund sharp@0.35.5 >/dev/null
@@ -43,7 +44,7 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
NEXT_TELEMETRY_DISABLED=1 \
PORT=3000 \
HOSTNAME=0.0.0.0 \
NEXT_SHARP_PATH=/sharp/node_modules/sharp
NODE_PATH=/sharp/node_modules
WORKDIR /app
# outputFileTracingRoot = корень репо, поэтому server.js лежит в apps/landing/ внутри standalone
COPY --from=build --chown=kvarta:kvarta /repo/apps/landing/.next/standalone ./
+1
View File
@@ -26,3 +26,4 @@
| 20 | Оркестрация | Оркестратор закоммитил ретранслятор через `git add -A` в том же чекауте, где дизайнер вёл незакоммиченную вёрстку шага 12 — почти весь шаг 12 уехал в коммит `20db25b` «ретранслятор Bot API», а на прод выкатилась половина вёрстки | Отчёт дизайнера | История переписана с согласия автора: `20db25b` разделён на «ретранслятор» и «секции», `push --force-with-lease`; впредь — `git add` по путям |
| 21 | Шаги 12–13, дизайн | Самопроверка дизайнера по своим же скриншотам пропустила: полупрозрачную подложку штампа (просвечивало фото), фильтр «краски», съевший половину даты, ряд hero-метрик, запрещённый его же DESIGN.md, и повтор «ЧТ» лентой и штампом на каждой этикетке | Независимый критик по скриншотам, без доступа к коду | Итерация критика 1 |
| 22 | Шаг 12, дизайн | Секция доставки свёрстана во всю ширину без привязки к контейнеру — «Самовывоз» на 1440 вылез за правый край на 24px | Критик по скриншоту 1440 | Колонки 7–12 контейнера 1280 |
| 23 | Шаг 5, образы | Оркестратор подключил sharp через `NEXT_SHARP_PATH` по памяти о старых версиях Next; Next 16 эту переменную не читает и при отсутствии sharp молча отдаёт оригинал — `w=750` весил 838 КБ JPEG | Lighthouse mobile на проде: «image delivery, savings 777 KiB», затем `curl` по `/_next/image` | `NODE_PATH=/sharp/node_modules`; проверено: тот же запрос — 151 КБ webp |