Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ The "tasks" feature is a reward checklist that grants users inner coins after ve
- **Cursor pointer everywhere**: All clickable elements/blocks/links/pickers/sliders must explicitly set `cursor-pointer` for clear affordance
- **Dialog affordance**: Dialog close buttons AND the dimmed overlay/backdrop used to close dialogs must have `cursor-pointer`
- **No duplicate API calls**: guard client-side fetch effects with stable fetch keys/in-flight refs so Strict Mode doesn’t trigger the same request multiple times (one request per dataset)
- **Honest template surfaces**: Never ship fake counters, `#` links, console-only submit handlers, or mock success actions on real routes. If a flow is not wired yet, hide it or render a disabled localized state with a clear reason.
- **Documentation**: Write documentation directly in code files as comments and docstrings, not as separated files (No new .md files to describe logic, usage, or implementation details; No example .json files to show data structures or logging formats)
- **Required fields UX**: For all forms mark required inputs with `*` and highlight missing/invalid required fields with a red focus/outline when the API returns validation errors (e.g., `detail` value). Keep visual feedback consistent across the app.
- **Popups/Toasts**: Emit only one localized toast per error; map backend `detail`/field keys to translated messages and surface the exact field causing the issue (no duplicate global+local toasts).
Expand All @@ -95,6 +96,7 @@ The "tasks" feature is a reward checklist that grants users inner coins after ve
- **Shared translations first**: Use existing `system.*` translation keys for shared labels (loading, refresh, common actions) instead of introducing feature-specific duplicates; migrate simple words from feature scopes to `system.*` when touching those areas.
- When adding new locale strings, ensure non-English locales are translated (avoid copy-pasting English into `ru`/`es`/`ar`/`zh`).
- **No duplicated UI/i18n**: If multiple screens/forms need the same control or helper text, extract a shared component in `web/src/shared/ui/` and move the strings to `system.*` (delete feature-scoped duplicates).
- **Delete dead frontend layers**: Remove unused demo slices, duplicate toast hooks, unused providers, and speculative wrappers instead of keeping parallel infrastructure in the runtime tree.
- **Unit suffixes**: Show measurement units using right-side labels/suffix segments on inputs (e.g., %, kg, cm); keep left labels clean.
- **Number inputs**: Hide browser stepper arrows and prevent scroll-wheel value changes; use shared Input defaults or equivalent handlers for any custom number fields.
- **Allow clearing inputs**: Form fields (including numeric inputs) must allow users to clear the value with Backspace/Delete before retyping; do not force immediate fallback values while typing.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Template Web App
Modern full-stack web application with Python FastAPI backend, Next.js frontend, Telegram bot, and Telegram / VK / MAX Mini App support. Built with Docker containers and featuring multilingual support, and production-ready flow.
# Launchpad Template
Reusable full-stack launch template with Python FastAPI backend, Next.js frontend, Telegram bot, and Telegram / VK / MAX Mini App support. Built with Docker containers, multilingual routing, and production-ready flows.

Reference pages and shared template surfaces should stay honest: ship only real links and real actions, or render disabled localized states until the backend contract is wired.

## Background tasks (Taskiq)
- Worker: `uv run taskiq worker tasks.broker:broker tasks.registry`
Expand Down
6 changes: 4 additions & 2 deletions web/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ COPY . .
# Set build environment
ENV NEXT_TELEMETRY_DISABLED=1

# Build the application and strip dev dependencies for runtime
RUN pnpm run build && pnpm prune --prod
# Build the application and strip dev dependencies for runtime.
# `pnpm prune` may trigger lifecycle scripts after removing dev deps;
# disable scripts to avoid husky/prepare failures in CI Docker builds.
RUN pnpm run build && pnpm prune --prod --ignore-scripts

# ========================================
# Development Stage - Development runtime with hot reload
Expand Down
52 changes: 37 additions & 15 deletions web/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,39 @@
"sections": "الأقسام"
},
"hub": {
"toastDemo": {
"title": "الإشعارات",
"description": "شغّل جميع أنواع التوست للتحقق من المظهر.",
"actions": {
"default": "افتراضي"
"description": "طبقة التفاعل الحي للغرف والدردشة والمكالمات والعمل التعاوني.",
"intro": {
"title": "ما الذي يمثله هذا القسم",
"body": "استخدم المركز كطبقة التفاعل الفوري في المنتج: الغرف، خيوط الدردشة، مكالمات الفيديو، اللوحات المشتركة، الحضور، وتدفقات التنسيق. يجب أن يبدو حيًا واجتماعيًا، لا تحريريًا ولا موجّهًا للدعم."
},
"modes": {
"title": "أنماط التفاعل",
"rooms": {
"title": "الغرف والمساحات",
"description": "غرف دائمة فيها أعضاء وأدوار وحضور وحالة غير مقروء وتبديل سريع بين السياقات."
},
"chat": {
"title": "الدردشة والخيوط",
"description": "تدفقات رسائل للدردشة المباشرة ونقاشات الغرف والردود والتفاعلات والتنسيق السريع."
},
"messages": {
"default": "إشعار افتراضي",
"success": "إشعار نجاح",
"error": "إشعار خطأ",
"warning": "إشعار تحذير",
"info": "إشعار معلومات",
"loading": "جارٍ التحميل..."
"calls": {
"title": "الصوت والفيديو",
"description": "تدفقات المكالمات مع حالة انضمام واضحة ووضع المشاركين والجدولة والسياق بعد المكالمة."
},
"boards": {
"title": "اللوحات والسبورات",
"description": "مساحات مشتركة للملاحظات والتعليقات والمراجعة والتفكير التعاوني."
}
},
"principles": {
"title": "قواعد المنتج",
"realtime": "ابنِ هذا القسم حول الحضور الفوري وحالة غير المقروء والكتابة وسياق المشاركين بدلًا من كتل المحتوى الثابتة.",
"lowNoise": "اجعل واجهة التفاعل بسيطة وبشرية: قوائم غرف واضحة وكثافة رسائل مريحة ومن دون ضوضاء زخرفية.",
"realStates": "اعرض فقط الحالات الحقيقية من الواجهة الخلفية أو طبقة النقل. لا تزوّر أعداد المتصلين أو نشاط الغرف أو مؤشرات المكالمات."
},
"activation": {
"title": "قبل إطلاق تدفقات التفاعل",
"body": "اربط الغرف والأعضاء والرسائل والمكالمات والصلاحيات واللوحات المشتركة أولاً بعقود خلفية حقيقية أو بطبقة نقل فورية. إذا لم تكن قدرة ما جاهزة بعد، فأخفها أو عطّلها مع سبب مترجم بوضوح."
}
},
"tasks": {
Expand Down Expand Up @@ -381,6 +400,8 @@
"next": "التالي",
"download": "تحميل",
"cancel": "إلغاء",
"ok": "حسنًا",
"confirm": "تأكيد",
"none": "لا يوجد",
"yes": "نعم",
"no": "لا",
Expand All @@ -392,6 +413,7 @@
"iconKeyPlaceholder": "home",
"iconKeyDescription": "أدخل مفتاح أيقونة FontAwesome (بدون بادئة 'fa-'). اتركه فارغاً لعدم استخدام أيقونة.",
"item": "عنصر",
"deleteItemConfirm": "هل تريد حذف \"{item}\"؟",
"processing": "جارٍ المعالجة",
"errors": {
"unsupportedImageFormat": "تنسيق الصورة غير مدعوم. حوّل الملف إلى ‎JPG‎ أو ‎PNG‎ ثم حاول مرة أخرى."
Expand All @@ -411,9 +433,9 @@
"smz": "عمل حر"
},
"brand": {
"name": "web",
"description": "تطبيق ويب نموذجي",
"tagline": "تطبيق ويب نموذجي",
"name": "Launchpad",
"description": "قالب إطلاق متكامل قابل لإعادة الاستخدام",
"tagline": "قالب إطلاق متكامل قابل لإعادة الاستخدام",
"address": "سانت بطرسبرغ، روسيا"
},
"footer": {
Expand Down
52 changes: 37 additions & 15 deletions web/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,39 @@
"sections": "Sections"
},
"hub": {
"toastDemo": {
"title": "Toasts",
"description": "Trigger each toast variant to verify styling.",
"actions": {
"default": "Default"
"description": "Live interaction layer for rooms, chat, calls, and collaborative work.",
"intro": {
"title": "What this area represents",
"body": "Use the hub as the realtime interaction layer of the product: rooms, chat threads, video calls, shared boards, presence, and coordination flows. It should feel live and social, not editorial or support-oriented."
},
"modes": {
"title": "Interaction modes",
"rooms": {
"title": "Rooms and spaces",
"description": "Persistent rooms with members, roles, presence, unread state, and fast context switching."
},
"chat": {
"title": "Chat and threads",
"description": "Message flows for direct chat, room discussion, replies, reactions, and lightweight coordination."
},
"messages": {
"default": "This is a default toast",
"success": "This is a success toast",
"error": "This is an error toast",
"warning": "This is a warning toast",
"info": "This is an info toast",
"loading": "Loading..."
"calls": {
"title": "Voice and video",
"description": "Call flows with clear join state, participant status, scheduling, and post-call context."
},
"boards": {
"title": "Boards and whiteboards",
"description": "Shared planning surfaces for notes, comments, review, and collaborative thinking."
}
},
"principles": {
"title": "Product rules",
"realtime": "Design this section around realtime presence, unread state, typing, and participant context instead of static content blocks.",
"lowNoise": "Keep interaction UI minimal and human: clear room lists, readable message density, and no decorative overload.",
"realStates": "Show only real backend or transport states. Do not fake online counts, room activity, or call metrics."
},
"activation": {
"title": "Before shipping interaction flows",
"body": "Wire rooms, membership, messaging, calls, permissions, and shared boards to real backend contracts or realtime transport first. If a capability is not ready yet, keep it hidden or disabled with a localized reason."
}
},
"tasks": {
Expand Down Expand Up @@ -381,6 +400,8 @@
"next": "Next",
"download": "Download",
"cancel": "Cancel",
"ok": "OK",
"confirm": "Confirm",
"none": "None",
"yes": "Yes",
"no": "No",
Expand All @@ -392,6 +413,7 @@
"iconKeyPlaceholder": "home",
"iconKeyDescription": "Enter a FontAwesome icon key (without 'fa-' prefix). Leave empty for no icon.",
"item": "Item",
"deleteItemConfirm": "Delete \"{item}\"?",
"processing": "Processing",
"errors": {
"unsupportedImageFormat": "Unsupported image format. Please convert the file to JPG or PNG and try again."
Expand All @@ -411,9 +433,9 @@
"smz": "Self-employed"
},
"brand": {
"name": "web",
"description": "Template web app",
"tagline": "Template web app",
"name": "Launchpad",
"description": "Reusable full-stack launch template",
"tagline": "Reusable full-stack launch template",
"address": "St. Petersburg, Russia"
},
"footer": {
Expand Down
52 changes: 37 additions & 15 deletions web/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,39 @@
"sections": "Secciones"
},
"hub": {
"toastDemo": {
"title": "Notificaciones",
"description": "Activa cada variante de toast para comprobar el estilo.",
"actions": {
"default": "Predeterminado"
"description": "Capa de interacción en vivo para salas, chat, llamadas y trabajo colaborativo.",
"intro": {
"title": "Qué representa esta área",
"body": "Usa el hub como la capa de interacción en tiempo real del producto: salas, hilos de chat, videollamadas, pizarras compartidas, presencia y flujos de coordinación. Debe sentirse vivo y social, no editorial ni orientado a soporte."
},
"modes": {
"title": "Modos de interacción",
"rooms": {
"title": "Salas y espacios",
"description": "Salas persistentes con miembros, roles, presencia, estado de no leído y cambio rápido de contexto."
},
"chat": {
"title": "Chat e hilos",
"description": "Flujos de mensajes para chat directo, discusión por sala, respuestas, reacciones y coordinación ligera."
},
"messages": {
"default": "Notificación predeterminada",
"success": "Notificación de éxito",
"error": "Notificación de error",
"warning": "Notificación de advertencia",
"info": "Notificación informativa",
"loading": "Cargando..."
"calls": {
"title": "Voz y video",
"description": "Flujos de llamadas con estado de unión claro, estado de participantes, agenda y contexto posterior."
},
"boards": {
"title": "Tableros y pizarras",
"description": "Superficies compartidas para notas, comentarios, revisión y pensamiento colaborativo."
}
},
"principles": {
"title": "Reglas del producto",
"realtime": "Diseña esta sección alrededor de presencia en tiempo real, no leídos, escritura y contexto de participantes, no como bloques de contenido estático.",
"lowNoise": "Mantén la UI de interacción mínima y humana: listas claras de salas, densidad legible de mensajes y sin sobrecarga decorativa.",
"realStates": "Muestra solo estados reales del backend o del transporte. No simules usuarios en línea, actividad de salas ni métricas de llamadas."
},
"activation": {
"title": "Antes de lanzar flujos interactivos",
"body": "Conecta primero salas, membresía, mensajería, llamadas, permisos y pizarras compartidas a contratos reales del backend o al transporte en tiempo real. Si una capacidad aún no está lista, mantenla oculta o deshabilitada con una razón localizada."
}
},
"tasks": {
Expand Down Expand Up @@ -381,6 +400,8 @@
"next": "Siguiente",
"download": "Descargar",
"cancel": "Cancelar",
"ok": "Aceptar",
"confirm": "Confirmar",
"none": "Ninguna",
"yes": "Sí",
"no": "No",
Expand All @@ -392,6 +413,7 @@
"iconKeyPlaceholder": "home",
"iconKeyDescription": "Ingresa una clave de icono FontAwesome (sin prefijo 'fa-'). Deja vacío para no usar icono.",
"item": "Elemento",
"deleteItemConfirm": "¿Eliminar \"{item}\"?",
"processing": "Procesando",
"errors": {
"unsupportedImageFormat": "Formato de imagen no admitido. Convierte el archivo a JPG o PNG y vuelve a intentarlo."
Expand All @@ -411,9 +433,9 @@
"smz": "Autoempleado"
},
"brand": {
"name": "web",
"description": "Aplicación web de plantilla",
"tagline": "Aplicación web de plantilla",
"name": "Launchpad",
"description": "Plantilla full-stack reutilizable para lanzamientos",
"tagline": "Plantilla full-stack reutilizable para lanzamientos",
"address": "San Petersburgo, Rusia"
},
"footer": {
Expand Down
Loading