diff --git a/Makefile b/Makefile
index 95d8cce6..435ea944 100644
--- a/Makefile
+++ b/Makefile
@@ -83,4 +83,27 @@ fix:
cargo fmt
cargo clippy --fix --allow-dirty --allow-staged
-.PHONY: check fmt fmt-check clippy test fix check-appcontext check-handler-deps check-handler-repos check-presentation-adapters check-federation-off-social check-social-crates-are-ap-free
+# Run the dev stack:
+# server (:3000, in the foreground, Ctrl-C stops it)
+# worker (background, log at /tmp/movies-diary-worker.log)
+# spa (:5173, background, log at /tmp/movies-diary-spa.log)
+# Tear down the background processes with `make dev-stop`.
+# .env enables EVENT_BUS_BACKEND=nats, so both binaries are built with the nats feature.
+dev: dev-worker dev-spa
+ cargo run -p server --features nats
+
+dev-stop:
+ -pkill -f '[t]arget.debug.worker' || true
+ -pkill -f '[s]pa/node_modules/[.]bin/vite' || true
+ @echo "dev stack stopped"
+
+dev-worker:
+ @bash -c 'nohup cargo run -p worker --features nats >> /tmp/movies-diary-worker.log 2>&1 & echo "worker → pid $$! (log: /tmp/movies-diary-worker.log)"'
+
+dev-spa:
+ @bash -c 'nohup npm --prefix spa run dev >> /tmp/movies-diary-spa.log 2>&1 & echo "spa → pid $$! (log: /tmp/movies-diary-spa.log, http://localhost:5173)"'
+
+spa-dev:
+ @npm --prefix spa run dev
+
+.PHONY: check fmt fmt-check clippy test fix dev dev-stop dev-worker dev-spa spa-dev check-appcontext check-handler-deps check-handler-repos check-presentation-adapters check-federation-off-social check-social-crates-are-ap-free
diff --git a/spa/src/locales/en.json b/spa/src/locales/en.json
index f187cfce..385b36cc 100644
--- a/spa/src/locales/en.json
+++ b/spa/src/locales/en.json
@@ -267,14 +267,14 @@
"seasons": "Seasons",
"mediums": "How you watch",
"rhythm": "When you watch",
- "ratingLiftUp": "You rate {{genre}} higher than your own average — {{genreAvg}}★ vs {{userAvg}}★",
- "ratingLiftDown": "You rate {{genre}} lower than your own average — {{genreAvg}}★ vs {{userAvg}}★",
+ "ratingLiftUp": "You rate {{genre}} higher than your own average — {{genreAvg}}★ vs {{userAvg}}★",
+ "ratingLiftDown": "You rate {{genre}} lower than your own average — {{genreAvg}}★ vs {{userAvg}}★",
"yourAverage": "your average",
- "seasonality": "{{genre}} is your {{month}} genre — {{multiplier}}× your yearly baseline",
- "mediumTiming": "You watch {{medium}} on weekends {{multiplier}}× more than your baseline",
+ "seasonality": "{{genre}} is your {{month}} genre — {{multiplier}}× your yearly baseline",
+ "mediumTiming": "You watch {{medium}} on weekends {{multiplier}}× more than your baseline",
"weekends": "weekends",
"yourBaseline": "your baseline",
- "rhythm": "You watch mostly {{start}}–{{end}} on {{days}}",
+ "rhythmBody": "You watch mostly {{start}}–{{end}} on {{days}}",
"filmsSample": "{{count}} films"
},
"wrapup": {
diff --git a/spa/src/routes/_app/insights.tsx b/spa/src/routes/_app/insights.tsx
index 06ada321..2584fcee 100644
--- a/spa/src/routes/_app/insights.tsx
+++ b/spa/src/routes/_app/insights.tsx
@@ -1,6 +1,6 @@
import { createFileRoute } from "@tanstack/react-router"
import type { ReactNode } from "react"
-import { useTranslation } from "react-i18next"
+import { Trans, useTranslation } from "react-i18next"
import {
CalendarDays,
Clapperboard,
@@ -26,6 +26,8 @@ export const Route = createFileRoute("/_app/insights")({
const MONTH_BASE = new Date(2024, 0, 1) // any year; only the month name matters
const MONDAY_BASE = new Date(2024, 2, 4) // 2024-03-04 is a Monday
+const hl =
+
function isKind(kind: K) {
return (insight: Insight): insight is Extract => insight.kind === kind
}
@@ -138,11 +140,17 @@ function RatingLiftCard({ insight }: RatingLiftCardProps) {
- {t(up ? "insights.ratingLiftUp" : "insights.ratingLiftDown", {
- genre: insight.genre,
- genreAvg: insight.genre_avg.toFixed(1),
- userAvg: insight.user_avg.toFixed(1),
- })}
+
+
+
@@ -161,17 +169,22 @@ function RatingLiftCard({ insight }: RatingLiftCardProps) {
}
function SeasonalityCard({ insight }: SeasonalityCardProps) {
- const { t } = useTranslation()
return (
- {t("insights.seasonality", {
- genre: insight.genre,
- multiplier: insight.multiplier.toFixed(1),
- month: monthName(insight.month),
- })}
+
+
+
@@ -194,10 +207,16 @@ function MediumTimingCard({ insight }: MediumTimingCardProps) {
- {t("insights.mediumTiming", {
- medium: mediumLabel(insight.medium, t),
- multiplier: insight.multiplier.toFixed(1),
- })}
+
+
+
@@ -216,17 +235,22 @@ function MediumTimingCard({ insight }: MediumTimingCardProps) {
}
function RhythmCard({ insight }: RhythmCardProps) {
- const { t } = useTranslation()
return (
- {t("insights.rhythm", {
- start: hourLabel(insight.peak_hour_start),
- end: hourLabel(insight.peak_hour_end),
- days: dayNames(insight.top_days).join(", "),
- })}
+
+
+